Building Enterprise JavaScript Applications
上QQ阅读APP看书,第一时间看更新

Modularizing our request handlers

At the moment, we only have one request handler for our POST /users endpoint, but by the end of this chapter, we will have implemented many more. Defining them all inside the src/index.js file would lead to a huge, unreadable mess. Therefore, let's define each request handler as its own module. Let's begin by creating a file at src/handlers/users/create.js and extract the Create User request handler into it. Previously, the request handler was an anonymous arrow function; now that it's in its own module, let's give it a name of createUser. Lastly, export the function in the same manner as we did with the middleware. You should end up with something like this:

function createUser(req, res) {
if (
!Object.prototype.hasOwnProperty.call(req.body, 'email')
|| !Object.prototype.hasOwnProperty.call(req.body, 'password')
) {
res.status(400);
res.set('Content-Type', 'application/json');
return res.json({ message: 'Payload must contain at least the email and password fields' });
}
...
}

export default createUser;

Then, import the createUser handler back into src/index.js and use it inside app.post:

...
import createUser from './handlers/users/create';
...
app.post('/users', createUser);
...

However, our request handler requires an Elasticsearch client to work. One way to resolve this would be to move the following lines to the top of the src/handlers/users/create.js module:

import elasticsearch from 'elasticsearch';
const client = new elasticsearch.Client({ host: ... });

However, thinking ahead, since we will have many request handlers, we shouldn't instantiate a separate instance of the client for each handler. Instead, we should create one Elasticsearch client instance and pass it by reference into each request handler.

To do this, let's create a utility function at src/utils/inject-handler-dependencies.js that takes in a request handler function and the Elasticsearch client, and returns a new function that will call the request handler, passing in the client as one of the parameters:

function injectHandlerDependencies(handler, db) {
return (req, res) => { handler(req, res, db); };
}

export default injectHandlerDependencies;

This is an example of a higher-order function, which is a function that operates on, or returns, other functions. This is possible because functions are a type of object in JavaScript, and thus are treated as first-class citizens. This means you can pass a function around just like any other object, even as function parameters.

To use it, import it into our src/index.js file:

import injectHandlerDependencies from './utils/inject-handler-dependencies';

Then, instead of using the createUser request handler directly, pass in the handler returned from injectHandlerDependencies:

# Change this
app.post('/users', createUser);

# To this
app.post('/users', injectHandlerDependencies(createUser, client));

Lastly, update the request handler itself to make use of the client:

function createUser(req, res, db) {
...
db.index({ ... });
}

Once again, run the E2E tests to make sure we have not introduced a bug, and then commit our changes:

$ git add -A && git commit -m "Extract request handlers into modules"