intertwingly

It’s just data

React Hooks as Middleware


I see a lot of use of React for client side single page apps. I see React for static server side generation. I see React for Server Side Rendering to be hydrated by client side apps. I even see some use of React for templates.

Unless I'm missing something, I don't see React often used as middleware. There is a subtle, but important, difference between using React as templates and as middleware.

With templates, your application logic resides in the web server, typically Express. After your application gathers up the relevant data, it them calls out to a template to render this data, and that rendering is returned as the response.

That's a fine model, but other models exist. In some models, the web server doesn't host application logic, instead it serves applications. This is basically the PHP, CGI, JSP/Servlet models and ASP. With this model, you deploy an application by dropping a file into folder containing code that has full access to the request and full control over the response.

Or, expressed in Express.js terms, middleware.

With PHP and JSP, there is absolutely no boilerplate logic required. Your application is HTML, optionally augmented by embedded logic.

With React and JSX, you can get real close to this ideal: applications would need only to import React and export a function that returns a response.

Here is an example of what such an application could look like:

import React from 'react';

export default request => {
let { name } = request.body;

return <html>
<head>
<title>Greeting demo</title>
</head>

<body>
<h1>Greeting Demo</h1>

{request.method === 'GET' ?
<>

<form method="post">
<label htmlFor="name">Enter your name: </label>
<input id="name" name="name"/>
</form>

</> : <>

<p>Hello {name}!</p>

</>}
</body>
</html>
}

Not a very extensive application, but it demonstrates the basic concepts.

If you visit the page (via HTTP GET), a form is displayed. Type in your name and press enter and a HTTP POST request is made. This second request is processed by the same application, the name you entered is extracted from the parameters, and displayed in the response.

It doesn't even take much work to make this possible. Here's the express middleware code required:

const React = require('react');
const ReactDOMServer = require('react-dom/server');

module.exports = path => async (request, response, next) => {
let app = null;

// load the app; otherwise skip to next in chain
try {
app = require(`./${path}${request.path}.js`).default;
} catch (error) {
if (error.code === 'MODULE_NOT_FOUND') {
return next();
} else {
return next(error);
}
}

// run the app
let output = app(request, response, next);

// optionally render and send the response
if (output && !response.writableEnded) {
if (React.isValidElement(output)) {
output = ReactDOMServer.renderToString(output);
}

response.send(output);
}
}

If the module is not found, next() is called, which ultimately could be processed by later middleware or even result in a 404.

The application itself is provided not only with the request, but also the response and next objects.

Finally, if the return value is a React element, it is rendered and sent. Other options include returning a string or nothing at all.

Should you want to play around with this, try it out on npm or github.