Cleaner routes with NestJs

If you’re beginning with Nest and you’ve followed the official documentation there is are few things that you could probably do better.

Returning newly created items

When you make a POST request on RESTful API it generally returns the newly created item.

What do I mean by that ?

POST /users

{
"firstName": "Murphy",
"lastName": "Eddie"
}

Should return 200 for a status code AND the following content:

{
"id": 378,
"firstName": "Murphy",
"lastName": "Eddie"
}

Returning the created item will be really useful when developing a application using your API.
For example, the application creates an user and store it using the RESTful API. Then let’s says a user changes its password. In that case, the application will have to do a PUT on /users/378, right ? But if your API doesn't return newly the created item, then to make the PUT request you won't know that 378 is the user id...

To achieve that, simply make your service return newly created item,

async create(user: User): Promise<User> {
return await this.userRepository.save(user);
}

and your controller do the same.

@Post()
async create(@Body() user: CreateUserDto): Promise<User> {
return await this.userService.create(user);
}

Returning updated content

It’s also important to return the updated item when making a PUT operation. It will ensure that the application got exactly the same data as your API.
It's also really nice to have some kind of visual confirmation when testing your PUT request with a tool like Postman.

From the user’s service, you could do it in two different ways:

  • save the received item in the database and then return it
  • save the item, fetch it from the database to finally return the fetched item

I prefer the latter. Even tho it adds one database operation by doing so, you make sure you really return what you stored in the database. While with the first option, really what you’re returning is just what the user sent you…

Also if there is some king of computing operation, performed in the entity of your item, the first option is not valid. For example: if you keep the date of the update in a field such as updatedAt. What you would do is use @BeforeUpdate annotation within UserEntity so the date gets updated at each insert. And if you simply return the application request like this:

await this.service.update(id, user);
return user;

Then the API’s user won’t get its item with updatedAt field set at the correct date.

A better way is to do it like this:

return await this.service.update(id, user);

Simply return what you saved in the database.

Wrong identifier

What if you try to delete an item that doesn’t exist ?

DELETE /users/87 (there is no user 87)

Perhaps it should return 404 not found error or a 400 bad request error instead. Otherwise, our application won't have a clue that something wrong happens. Instead, it will receive 200 and believe DELETE operation went well (if we just follow nest documentation examples).

The Same thing applies for a PUT request when trying to update an item with the wrong id. In fact, I think it's more likely to occur. For example: if we try to update an item that has been already deleted; or that hasn't been saved yet. It could happen in various scenario:

  • the connection broke during saving; our application didn’t handle the error correctly and believe our user is saved
  • with shared resources among different user: one delete it, while the other doesn’t know it got deleted
  • offline cache did not synchronize with remote API: the user called API from its phone got items in cache; then log onto from its laptop remove it; later get back to his phone then try to update it (item still in cache)

To fix this and return 404 not found we simply have to try to fetch the item before to delete/update it, and if we don't find it we just throw a 404 not found error.

And here is what our service looks like:

async delete(id: any): Promise<User> {
let user = await this.findById(id)
if (user == null) {
throw new HttpException(
`invalid id: ${id}`,
HttpStatus.NOT_FOUND
);
}
await this.userRepository.delete(user);
return user;
}
async update(id: any, user: User): Promise<User> {
// trying to update a user with an invalid id ?
if (await this.findById(id) == null) {
throw new HttpException(
`invalid id: ${id}`,
HttpStatus.NOT_FOUND,
);
}
await this.userRepository.update(id, user);
return await this.findById(id);
}
private async findById(id: number): Promise<User> {
return await this.userRepository.findOne({
where: [{ id: id }],
});
}

POST /UpdateOrCreate endpoint

One good thing to know is that if you try to update an item that doesn’t exist Typeorm will just create it for you, which can lead to confusing situations.
That leads me to think it’s pretty easy to create an /UpdateOrCreate endpoint.

All it takes is the following in your controller:

return await this.service.update(id, user);

And that in your service:

async updateOrCreate(id: any, user: User): Promise<User> {
await this.userRepository.update(id, user);
return await this.findById(id);
}

You may have come across this kind of endpoint when using some web API. It simply does what it says : create the item if it doesn’t exist otherwise just update it.

Farewell

Well, that’s it. I hope you enjoyed this article. Give me some clap if you would like to read more about NestJs from me.

Oh, I almost forgot! You’ll find a code example at this github repository

self-taught programmer; passionate about Rust & Blockchains