🔌 Day 6 — Handle HTTP Requests With Middlewares
20 min read·Jan 1, 2026
Welcome to Day 6.
In yesterday's lesson, you created an API endpoint that returns notes over HTTP.
In today's lesson, you'll add the ability to create notes through the API, and you'll start enforcing rules on incoming requests.
You'll learn the purpose of middleware, how to validate input before it reaches your endpoint logic, and how to return the right HTTP status code when the client sends bad data.
By the end of this lesson, your API will support both reading and writing notes, with basic validation in place.
Implement a POST /note endpoint
Within the server.js file, let's declare a new HTTP POST /note endpoint whose role is to save a note send by a client in the form of an HTTP request into the same JSON file used by the CLI tool.
import express from 'express';
import path from 'node:path';
import loadEnv from '../config/load_env.js';
import { readJSON, writeJSON } from '../storage/json_fs.js';
const server = express();
loadEnv();
const NOTES_FILE_PATH = path.resolve(process.env.LB_NOTES_FILE_PATH || 'data/notes.json');
const SERVER_PORT = parseInt(process.env.LB_SERVER_PORT, 10);
server.get('/notes', (req, res) => {
// ...
});
server.post('/note', (req, res) => {
//
});
server.listen(SERVER_PORT);
In Express, to access and use the data present in the message body of an incoming HTTP request — in this case, the note the user wants to save — we must use a special function called a "body-parsing middleware", whose role is to parse the data and store it into the body property of the request object.
Since the note is provided as a string, we can parse it by invoking the express.text() function, right before the request handler function.
// ...
server.post('/note', express.text(), (req, res) => {
//
});
server.listen(SERVER_PORT);
Notes:
- In web development, HTTP POST endpoints are generally used to send data to a server.
- In Express, the
.post()method is used to defined an HTTP POST endpoint.
Then, let's implement the same logic as the one used in the add() function of the CLI tool, however:
- Instead of returning upon success, let's respond to the client with an HTTP
200 OKcontaining an object with anotesproperty. - Instead of throwing upon error, let's respond to the client with an
HTTP 500 Internal Server Errorcontaining an object with anerrorproperty.
// ...
server.post('/note', express.text(), (req, res) => {
try {
let data = readJSON(NOTES_FILE_PATH);
let note = req.body.trim();
if (data === null) {
data = { notes: [] };
}
else if (data?.notes === undefined || !Array.isArray(data?.notes)) {
throw new Error('Invalid JSON file');
}
else if (note.length === 0) {
throw new Error('Invalid note');
}
data.notes.push(note);
writeJSON(NOTES_FILE_PATH, data);
res.json({ message: 'Note saved' });
} catch(error) {
res.status(500).json({ error: error.message });
}
});
server.listen(SERVER_PORT);
Introduction to middleware functions
In Express, a middleware is a function that allows you to intercept an incoming request before it reaches the endpoint's request handler function.
In practice, middlewares are used for a variety of actions, such as logging, validating, parsing, and more.
Just like a request listener, a middleware has access to the request and response objects, as well as an additional parameter named next.
function middleware(req, res, next) {
// ...
}
A middleware can essentially do one of two things:
-
Send an HTTP response to the client before the HTTP request reaches the request handler function.
function middleware(req, res, next) { // Process HTTP request... res.json({ error: 'Invalid payload' }); } -
Forward the HTTP request to the request handler function by executing the
next()function parameter.function middleware(req, res, next) { // Process HTTP request... next(); }
Middlewares are declared after the endpoint's path and before the request handler.
function middleware(req, res, next) { /* ... */ }
function handler(req, res) { /* ... */ }
server.get('/path', middleware, handler);
Implement a validation middleware
Within the src/api directory, let's create a new directory named middlewares, and within it, a new file named validator.js.
~/projects/
└─ lb_notes/
├─ .env
├─ bin/
├─ package.json
└─ src/
├─ api/
│ ├─ middlewares/
│ │ └─ validator.js
│ └─ server.js
├─ ...
Within this file, let's export a validator() function whose role is to check if the note included in the message body of a request is valid before being processed by the request handler.
export default function validator(req, res, next) {
const note = req?.body?.trim();
if (!note || !note.length) {
res.status(400).json({ error: 'Invalid note' });
} else {
req.body = note;
next();
}
}
When executed, it will:
- Attempt to remove whitespace from both ends of the message body string.
- Respond with an HTTP
400 Bad Requestcontaining an error message if the message body string is undefined or empty. - Otherwise, update the value of the message body string and forward the request to the request handler function.
Update the POST /note endpoint
Let's import the validator.js module into the server.js file and declare it after the express.text() middleware function.
// ...
import validator from './middlewares/validator.js';
server.get('/notes', (req, res) => {
// ...
});
server.post('/note', express.text(), validator, (req, res) => {
// ...
});
server.listen(SERVER_PORT);
Then, let's update the HTTP POST /note endpoint the following way by removing:
- Removing the declaration of the
notevariable. - Removing the conditional statement that checked the length of the
notevariable. - Adding the
req.bodyproperty validated by thevalidator()middleware function directly into thedata.notesarray.
// ...
server.post('/note', express.text(), validator, (req, res) => {
try {
let data = readJSON(NOTES_FILE_PATH);
if (data === null) {
data = { notes: [] };
}
else if (data?.notes === undefined || !Array.isArray(data?.notes)) {
throw new Error('Invalid JSON file');
}
data.notes.push(req.body);
writeJSON(NOTES_FILE_PATH, data);
res.json({ message: 'Note saved' });
} catch(error) {
res.status(500).json({ error: error.message });
}
});
server.listen(SERVER_PORT);
Test the API
First, let's kill the server using CTRL+C then restart it using the npm run server command.
~/projects/lb_notes$ npm run server
> lb_notes@1.0.0 server
> node src/api/server.js
^C
~/projects/lb_notes$ npm run server
Then, in a second terminal window, let's use the curl command to send an HTTP POST request to the /note endpoint containing a note in its message body, which should respond with an HTTP 200 OK containing a success message.
~/projects/lb_notes$ curl -i -X POST -H "Content-type: text/plain" -d "Get the bins out on Tuesdays" 127.0.0.1:5000/note
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 24
ETag: W/"18-z2AcFvBJt2HMaLhtWr2yfBDBnB4"
Date: Tue, 03 Feb 2026 11:36:23 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"message":"Note saved"}
Notes:
- The
-Xflag is used to set the HTTP method of the request (e.g., GET, POST).- The
-Hflag is used to set the HTTP headers of the request. Here,Content-type: text/plainindicates that the message body of the request is encoded as plain text (i.e. a string).- The
-dflag is used to set the message body of the request. Here, the string"Get the bins out on Tuesdays".
And let's send a second request with an empty message body — which means an empty note — to which the server should respond with an HTTP 400 Bad Request containing an error message.
~/projects/lb_notes$ curl -i -X POST -H "Content-type: text/plain" -d "" 127.0.0.1:5000/note
HTTP/1.1 400 Bad Request
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 24
ETag: W/"18-VYaY2UlPP/yB42cHLp+GHUZq4ho"
Date: Tue, 03 Feb 2026 11:52:38 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"error":"Invalid note"}