2
\$\begingroup\$

To learn the ropes of Node.js and Express, I'm trying to implement a very simple REST API. The API says what it should do instead of doing it, so I can focus on the framework's structure. The requirement for the API is the following:

  • Mock API for a question and answer game.
  • /qanda endpoint: Repository of all question-answer pairs.
  • /qanda/questionId endpoint: A specific question-answer pair.
  • Should handle HTTP verbs GET, POST, PUT, DELETE.
  • Assume HTTP request body is JSON.

All this is arbitrary. I'm just trying to get the hang of dealing with and parsing of HTTP requests and responses. To keep things organized I'm also using Express Router and Node.js modules.

Here is index.js

'use strict';
const express = require('express');

const qanda = require('./routes/qanda.js');

const port = 9999;
const app = express();

app.use('/qanda', qanda);

app
  .all('/', (req, res, nxt) => {
  res
    .status(200)
    .send('Welcome!');
  })
  .listen(port, () => {
    console.log(`Listening to localhost:${port}`);
  });

And here is ./routes/qanda.js

'use strict';
const express = require('express');

const qandaRouter = express.Router();
qandaRouter.use(express.json());

qandaRouter
  .route('/')
  .get((req, res, nxt) => {
    res
      .status(200)
      .send('Sending all the qandas!');
  })
  .post((req, res, nxt) => {
    res
      .status(200)
      .send(`Adding ${req.body.q} = ${req.body.a}`);
  })
  .put((req, res, nxt) => {
    res
      .status(405)
      .send('PUT not supported.');
  })
  .delete((req, res, nxt) => {
    res
      .status(200)
      .send('Deleting all qands!');
  });

qandaRouter
  .route('/:qId')
  .get((req, res, nxt) => {
    res
      .status(200)
      .send(`Sending qanda ${req.params.qId}`);
  })
  .post((req, res, nxt) => {
    res
      .status(405)
      .send('POST not supported.');
  })
  .put((req, res, nxt) => {
    res
      .status(200)
      .send(`Updating ${req.params.qId} with ${req.body.q} = ${req.body.a}`);
  })
  .delete((req, res, nxt) => {
    res
      .status(200)
      .send(`Deleting qanda ${req.params.qId}`);
  });

module.exports = qandaRouter;

I'm looking forward to all kind of feedback! From style, to best practices, anti-patterns, whatever you can think of is very much appreciated! I do have some specific questions:

  • Is method chaining the usual way to handle something like this in Express?
  • Is the method chaining style readable and maintainable?
  • Is there a more concise way to achieve this?
  • What would you do differently?
\$\endgroup\$
6
\$\begingroup\$

After I examine the codes you have written, I could see some things that are not rightly done. Below are the few things I highlighted:

  • The structuring of the codebase is not okay hence it can be maintainable and scalable if the codebase grows.
  • The RESTful API naming convention is not an acceptable one. Endpoints of any REST API should contain only resources (nouns) and use HTTP methods (verbs) for actions. You named your endpoint as /qanda instead of /qandas. I recommend you read this RESTful API Designing guidelines by Mahesh Haldar posted at Hackernoon. It's a great resource for developers.
  • Thirdly, you did not separate your server from express; they are both coupled together. It's not best practice to do so.
  • When API is being consumed by other applications, upgrading the APIs with some changes would also lead to breaking the existing services contract - this has been noticed by a lot of applications that provide API services. It is a best practice to always prefix all your URLs with /api/v1/users . If there is any major breaking update, we can name the new set of APIs as v2, v3, or v1.X.X. respectively.

From my experience, this is how I would approach it:

  • Firstly, I will create a directory called, qanda-api, inside it, I'll create other directories called src and two files app.js and server.js that reside inside the root directory with src
  • Secondly, inside src I will create two sub-directories called routes and controllers
  • Thirdly, I will initialize an npm registry in the root of the project directory that keeps track of all npm installed packages by typing the command below via the terminal:

npm init

  • After that, I will install the three dependencies that I will be using via the same terminal, but this time you must have internet access:

npm install express dotenv morgan

  • If the above process executes successfully, you will see the installed packages inside a file called package.json

Now let's do the coding by getting our hands dirty

  • Let's first create a file named controllers/qandaController.js in other to write our qanda logic that will be exported to its route file.
exports.getAllQandas = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Documments retrieved successfully',
        data: 'The data goes here from the data!'
    });
};

exports.getQanda = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Doc retrieved successfully',
        data: 'The data goes here from the data!'
    });
};

exports.createQanda = (req, res) => {
    res.status(201).json({
        status: 'success',
        message: 'Qanda created successfully',
        data: 'Return the created data here!'
    });
};

exports.updateQanda = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Doc updated successfully',
        data: 'The updated data goes here!'
    });
};

exports.deleteQanda = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Doc deleted!',
        data: 'Return the Deleted data'
    });
};
  • Now, it's time for us to create our qanda route in routes/qandaRoutes.js. We will then import the controller logic in its route handler below.
const express = require('express');
/**
 * import the qanda controller
 */
const qandaController = require('./../controllers/qandaController');

const router = express.Router();

router
    .route('/')
    .get(qandaController.getAllQandas)
    .post(qandaController.createQanda);

router
    .route('/:id')
    .get(qandaController.getQanda)
    .patch(qandaController.updateQanda)
    .delete(qandaController.deleteQanda);

module.exports = router;
  • Let's open the file we created earlier on called app.js in other to make use of the express we had installed. The content of this file is only for express which is then exported to its server:
const express = require('express');
const morgan = require('morgan');

const qandaRouter = require('./routes/qandaRoutes');

const app = express();
    
if (process.env.NODE_ENV === 'development') app.use(morgan('dev'));

// ROUTES
app.use('/api/v1/qandas', qandaRouter);
    
/*
 ** HANDLING UNHANDLED ROUTES
 */
app.all('*', (req, res, next) => {
    res.status(404).json({
        status: 'fail',
            message: `Can't find ${req.originalUrl} on this Server!`
    });
});
   
module.exports = app;
  • Let's create our server which becomes our entry point to the application. This where the actual execution happens whereby the exported app.js get used by inserting it as an argument in the createServer() function.
require('dotenv').config({ path: '.env' });
const http = require('http');

const app = require('./app');

const port = process.env.PORT || 5000;

const server = http.createServer(app);

server.listen(port, () => console.log(`App running on port ${port}`));

Conclusion

It's highly recommended to always have separation of concerns while writing codes and to aptly have a clean codebase that is maintainable and scalable over time. There's more to code organization when it comes to API design, one of it is the DRY principle (Don't Repeat Yourself). If you want to learn more about API design in Node.JS I recommend you take this course by Jonas. He demystifies the abstract concepts and taught best practices to the core. I've wrapped this answer with API design projects in Github: API and Tourism API. Feel free to clone or modify as you need.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I move the all routes in its own file called router.js as they grow over the time when we have mulitple versions of API. I then use that file in app.js like this require('./routes')(app). \$\endgroup\$ – SamFast Aug 24 '20 at 3:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.