Let a Node.js backend consume and produce JSON
Tags: Rest Api, Typescript, Nodejs, Json

The first thing you want to do after creating a hello world REST service using TypeScript, Node.js and Express.js is figure out how to let it consume and produce JSON.

Grabbing the template from last blog post

If you didn’t follow the previous blog post, make sure you have:

On GitHub, do a git clone https://github.com/Leejjon/node-ts-again and then switch to the 1.2 tag by running: git checkout tags/1.2

Verify that it works:

Run: npm install

Open a new terminal and run: npm run webpack

Run: npm start

Browse to http://localhost:8080/ and it should say “”Hello Leon!” or whatever name you entered when following the previous post.

So what do we want to produce and consume?

We will create a sample endpoint on localhost:8080/comments, on which we can do a POST request with JSON in the body to store a comment. The response body will contain the newly generated comment id and timestamp. Normally a comment would be stored in a database so we can retrieve the comment later via GET requests, but let’s leave that out for now (maybe for a future blog post? I need to test the Firebase API anyway).

Restructuring the project

Besides the existing index.ts in our ‘src’ folder, I’d recommend creating a folder for the controller and one for the model:

Creating a request handler function

Right now we have a request handler in the index.ts itself, but I think it’s nice to put request handlers in a separate file. Create a CommentsController.ts file in a controller directory. Put a function there with a fitting name for storing a comment:

import {Request, Response} from "express";



export const postComment = async (req: Request, res: Response) => {

res.send('Hello POST');

}

Now we need to tell Express to forward POST requests on /comments to this new function.

import express from "express";

import { postComment } from "./controller/CommentsController";



const PORT = process.env.PORT || 8080;



const app = express();



app.use(express.json());



app.post("/comments", postComment);



const server = app.listen(PORT, () => {

console.log(`Listening on port ${PORT}`);

});

Defining interfaces for the request and response

Below is what first came to my mind. I’d create a Comment.ts file in the model directory that exports these interfaces.

export interface NewCommentRequest {

message: string,

nickname: string

} export interface CommentConfirmation {

id: number,

timestamp: Date

}

Using the interfaces to consume and produce JSON

So let’s do a very basic implementation in the CommentsController.ts that doesn’t really do anything with the request, but parses it and responses with a valid CommentConfirmation:

import {Request, Response} from "express";

import {CommentConfirmation, NewCommentRequest} from "../model/Comment";



export const postComment = async (req: Request, res: Response) => {

try {

const newCommentRequest: NewCommentRequest = req.body;

console.log(`Stored comment from ${newCommentRequest.nickname}.`);

let response: CommentConfirmation = {id: 'commentId', timestamp: new Date()};

res.status(200);

res.send(response);

} catch (error) {

res.status(400);

res.send("Invalid request.");

}

}

To test this you can use curl:

curl -d '{"message": "Hi", nickname:"Leejjon"}' -H "Content-Type: application/json" -X POST http://localhost:8080/comments; echo

Or via Postman:

Error scenarios

So let’s take a look what happens if somebody sends incorrect JSON, for example by removing the first curly brace of the request body:

On the server logs you’ll see this:

If you want to test this using curl:

curl -d '"message": "Hi", "nickname":"Leejjon"}' -H "Content-Type: application/json" -X POST http://localhost:8080/comments; echo

What happened?

This is caused by the code we added to index.ts in the previous step:

app.use(express.json());

This added a handler that started to parse the bodies of incoming requests to JSON. The default behavior for incorrect JSON syntax seems to be to return a response with HTTP status 400 and the stacktrace in the body, while also printing the stacktrace in your server log.

While this is an acceptable default for proof of concepts, this is undesired in production environments behavior for two reasons:

Information leakage. My project is open source so it doesn’t really bother me that the client receives my stacktrace, but companies with closed source software often don’t want to expose this information to potential attackers.

It spams your log file. If you write the front end that makes requests to your backend you should make sure it always sends valid JSON. If people ignore your frontend and start sending invalid syntax on their own via curl or Postman, you mostly don’t care about the detailed stacktrace it caused. You are more interested in what they did send.

The quick and easy solution

The solution that the official Express.js documentation and stackoverflow seem to recommend is to write a custom error handler. Therefore it’s probably a best practice. I found a simpler solution though. OliverJAsh on GitHub suggested just parsing the requests as text and parsing JSON in the POST function itself. This way you have full control.

In index.ts, replace this line:

app.use(express.json());

By:

app.use(express.text({ type: 'application/json' }));

Now simply wrap the JSON.parse() method around the req.body in the CommentsController:

import {Request, Response} from "express";

import {CommentConfirmation, NewCommentRequest} from "../model/Comment";



export const postComment = async (req: Request, res: Response) => {

try {

const newCommentRequest: NewCommentRequest = JSON.parse(req.body);

console.log(`Stored comment from ${newCommentRequest.nickname}.`);

let response: CommentConfirmation = {id: 'commentId', timestamp: new Date()};

res.status(200);

res.send(response);

} catch (error) {

res.status(400);

res.send("Invalid request.");

}

}

If you find yourself in a situation where you get lots of “Invalid Requests” in your server logs and you don’t know what’s causing this. You can log the request bodies in your catch clause to figure out what’s going wrong:

} catch (error) {

if (error instanceof SyntaxError) {

console.log(req.body);

}

res.status(400);

res.send("Invalid request.");

}

This will spam your logs with incoming request bodies, but will give you an idea of what incorrect JSON is coming in. I made a tag on GitHub for it so you can use the code with this “quick and easy” solution.

Creating our own Error Handler

In the situation above we handled the JSON parsing in the postComment function. But parsing the JSON and handling errors doesn’t really have anything to do with the business logic of the postComment function, which should be “to post a comment”. Let’s check this Stackoverflow answer on how you could handle the errors on a different way:

I recommend you to read the official Express documentation on Error Handling and creating your own error handler:

Let’s place an error handler like the one above in our index.ts file. Unfortunately TypeScript won’t accept these parameters if we do that.

TS7006: Parameter ‘err’ implicitly has an ‘any’ type.

When using TypeScript, we need to define the types like this:

import express, {Request, Response, NextFunction} from "express"; import {postComment} from "./controller/RequestController"; const PORT = process.env.PORT || 8080; const app = express(); app.use(express.json()); app.use((err: Error, req: Request, res: Response, next: NextFunction) => {

// This check makes sure this is a JSON parsing issue, but it might be

// coming from any middleware, not just body-parser:

if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {

console.error(err);

return res.sendStatus(400); // Bad request

} // If it's another error, let the default handler handle it.

next();

}); // Rest of the code...

But even with the types defined in this error handler, it complains with TS239: Property 'status' does not exist on type 'SyntaxError'.

The status and body properties don’t seem to be defined in the type definitions of the SyntaxError. To solve this I created an interface that extends SyntaxError and defines the status and body fields. Next, I cast the ‘err’ object to a SyntaxErrorWithStatus object to validate the status.

import express, {Request, Response, NextFunction} from "express";

import {postComment} from "./controller/CommentsController"; const PORT = process.env.PORT || 8080; const app = express(); app.use(express.json()); interface SyntaxErrorWithStatusAndBody extends SyntaxError {

status: number;

body: string;

} app.use((err: Error, req: Request, res: Response, next: NextFunction) => {

// This check makes sure this is a JSON parsing issue, but it might be

// coming from any middleware, not just body-parser:

if ((err as SyntaxErrorWithStatusAndBody).status === 400 && 'body' in err) {

const bodyWithInvalidJson = (err as SyntaxErrorWithStatusAndBody).body;

console.error(`Somebody tried to do a request with invalid json: ${bodyWithInvalidJson}`);

return res.sendStatus(400); // Bad request

}

// If it's another error, let the default handler handle it.

next();

});

After the casting I log the malformed body of the request that we tried to parse and send a response with the 400 status code.

You can test it again with the curl command:

curl -d '"message": "Hi", "nickname":"Leejjon"}' -H "Content-Type: application/json" -X POST http://localhost:8080/comments; echo

The code

Find the complete code so far here on GitHub. Learn how to do additional validation on the incoming JSON in my third blog post here.