Back-End

Northcoders News API

Completion Date: Jan 2022

Northcoders News API
An example of the endpoints available in the Northcoders News API

This news project is a REST API that clones the back-end functionality of Reddit using Node.js and Express to serve articles, users, topics and comments in JSON format using a PostgreSQL database. There is functionality for creating, reading, updating and deleting items.


Technology

  • Node.js
  • Express
  • PostgreSQL
  • Jest
  • Supertest
  • Hosted with Heroku

These tools are all extremely popular with developers and employers, all ranking extremely highly in the Stack Overflow developer surveys as highly used.


Key Features

  • Testing

The app is tested with Jest and Supertest. There are two separate testing files - app.test.js and utils.test.js. The app.test.js file is connected to the database, while utils.test.js is for non-database helper functions - e.g. formatting data before seeding.

__tests__/app.test.js
// Testing a GET request to the /api/topics endpoint with Supertest
describe('/api/topics', () => {
  describe('GET', () => {
    test('200 - responds with an array of topic objects', async () => {
      const test = await request(app).get('/api/topics').expect(200);
      const topics = test.body.topics;
// Testing the shape of the returned array
      expect(topics).toBeInstanceOf(Array);
      expect(topics).toHaveLength(3);
      topics.forEach(topic => {
        expect(topic).toMatchObject({
          slug: expect.any(String),
          description: expect.any(String),
        });
      });
    });
  });
});
  • CI / CD

Set up using Github Actions. A workflow (pipeline) is set up to run tests each time the main branch is pushed. Once the integration tests have passed, the app is deployed.

Github Actions Test and Deploy
Github Actions successful test and deploy
  • Model View Controller Pattern

Following the MVC pattern to abstract the server into identifiable sections helps to debug errors and keep maintainable code.

The Controller handles the client request and invokes the model with the information contained in the request.

The Model handles interaction with the database to create, read, update and delete data.

The View is handled with the front-end Northcoders News project.

article-get-request
// 1. Controller handles GET request
const getArticles = async (req, res, next) => {
  try {
    const { sort_by, order } = req.query;
// 2. Model is invoked with information from request
    const articles = await selectArticles(sort_by, order);
// 4. Returned articles from database sent to front-end View
    res.status(200).send({ articles });
  } catch (err) {
    next(err);
  }

/* 3. Model queries the database and returns matching articles
   (simplified example for brevity) */
  const selectArticles = async (sort_by, order) => {
  let queryStr = `
  SELECT articles.*, COUNT(comment_id) AS comment_count
  FROM articles
  LEFT OUTER JOIN comments
  ON articles.article_id = comments.article_id
  GROUP BY articles.article_id
  ORDER BY ${sort_by} ${order};`;

  const { rows } = await db.query(queryStr);
  const articles = rows;

  return articles;
};
  • Error handling middleware

Errors in the project are passed between error-handling blocks with use of next function. This allows useful errors to be sent back to the user.

app.js
/* Example of an error handling block. If matched it will send the status and
   message to the user, or will pass the error to the next block*/
app.use((err, req, res, next) => {
  const psqlErrorCodes = ['22P02', '23502'];
  if (psqlErrorCodes.includes(err.code)) {
    res.status(400).send({ msg: 'Bad Request' });
  } else if (['23503'].includes(err.code)) {
    res.status(404).send({ msg: 'Resource not found' });
  } else next(err);
});

Problems I faced

While testing the project, I was having trouble with one of my tests failing. The test logic looked good and the behaviour was inconsistent, sometimes it would pass and other times it would fail.

I had two testing files set up – an app file for the endpoints, and a utils file for utility functions e.g. ones that would transform data before seeding. I eventually figured out that in one of my utility functions I needed a connection to the database, but I hadn’t set it up in that file. This led to inconsistent behaviour, where if I ran both test files at the same time it would work (due to the connection in the app file) and separately the utils file would fail.

This taught me to be careful with how I separate functions into test files, and checking which data they might need to access to successfully run.


What I learnt

  • Creating a relational database, setting up tables and inserting data

When creating the tables, I had to consider the order to create them, with tables that referenced others being created later. It was important to establish the data type for each row, and which rows required constraints, such as the slug and description rows below being specified as 'NOT NULL'. Rows that reference another table are set up as foreign keys. This process has helped to improved my SQL skills.

seeds/seed.js
// Creation of the topics table
await db.query(`CREATE TABLE topics (
    slug VARCHAR(50) PRIMARY KEY NOT NULL,
    description VARCHAR(500) NOT NULL
  );`);
  • How to seed database

The database connection was set up using connection pooling. Raw data was mapped to the required format, and then inserted into the table.

seeds/seed.js
// Formatting data pre-insertion (in utils.js)
formattedTopics = topicData => {
  return topicData.map(topic => {
    return [topic.slug, topic.description];
  });
};

// Data being inserted into the table using node-postgres
  async function insertTopics() {
    const formatTopic = formattedTopics(topicData);
    const sql = format(
      `INSERT INTO topics
      (slug, description)
      VALUES %L
      RETURNING *;`,
      formatTopic
    );
    await db.query(sql);
  }
  • Hosting with Heroku

Environmental variables are added on the Heroku dashboard and are referenced in the 'seed:prod' script to seed the Heroku database.


What I would improve

I would add more functionality to the project - some additional features to implement would be voting on comments, posting and deleting articles.

Setting up pagination would be a great next step, and would improve the front end functionality of the site.

Ben Weston

Built with Next.js, Tailwind and MDX