Introduction to Structuring APIs and unit tests in Express
This tech bite will show you how to structure your JavaScript back-end code in such a way it is easier to search through, modular, scalable, and allows for writing unit tests.
Writing APIs using Express.js initially seems quick and easy: it’s just basic routing and handling HTTP requests. A developer registers the HTTP request method with a certain path and handles the request using a function containing request and response objects.
The code below is an example of an Express app taken from Express.js documentation. The app starts the server and listens on port 3000 for incoming connections.
app.js const express = require('express'); const app = express(); const port = 3000; app.listen(port, () => { console.log(`Example app listening on port ${port}`); });
Routing refers to how an application’s endpoints (URLs) respond to client requests. The following example illustrates a simple route, responding with ‘Hello World!’.
app.js app.get('/hello, (req, res) => { res.send('Hello World!'); });
Generally, APIs are grouped in some way. Usually, they are grouped by controllers, and they perform similar tasks. Using the Express Router class, we can create a group of APIs with their own routing and middleware.
express.Router class provides a way to create modular and mountable route handlers. A Router instance is a complete middleware and routing system. Using Router class, code can be separated semantically, with each Router having a set of route definitions and common middleware.
routes/exampleRouter.js
const router = express.Router(); router.use((req, res, next) => { console.log('Time: ', Date.now()); next(); }) router.get(‘/hello’, function (req, res) { res.send('Hello World!'); });
Using routers is a great way of organizing code. Multiple route handlers are in the same file and have the common middleware. This approach has two problems: router files can become very large and hard to search through, and it’s hard to write unit tests for route definitions. The alternative is extracting the anonymous request handler function as a standalone function:
router.get(‘/hello’, onHello); controllers/example/onHello.js function onHello (req, res) { res.send('Hello World!'); });
Having a request handler as a separate function allows us to have each function in a separate file. This way, it’s easier to search them and enables us to write unit tests for each one. On the other hand, the router file remains relatively small.
Below is a pseudo-code example of a unit test for the ‘hello’ request handler. Consider using some testing JS frameworks and tools, like Mocha, Sinon, Proxyquire, and similar, when writing unit tests.
tests/controllers/example/onHello.js describe(‘Test onHello’, () => { it (‘Returns hello’, () => { onHello(req, res); expect.res.to.be.equal(‘Hello’); } }
Considering all this, we can create a file structure like the one shown below. This way, app.js remains a file where the application is set up. Business logic is separated into routes and controllers folders, which can be further grained using business and data access layers. Unit tests are separated in the tests folder, not affecting the general code structure.
src/ ├── app.js ├── routes/ │ └── exampleRouter.js ├── controllers/ │ └── example/ │ └── onHello.js └── tests/ └── controllers/ └── example/ └── onHello.test.js
This structure makes code much more organized and easier to search through, is modular, and allows scaling and writing unit tests and organizing them in a similar fashion.
“Structuring APIs and unit tests in Express” Tech Bite was brought to you by Dejan Anđelić, Software Engineer at Atlantbh.
Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.