loading...
ITNEXT

Creating your first Node.js REST API with Nest and Typescript

softchris profile image Chris Noring Updated on ・8 min read

Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris

A progressive Node.js framework for building efficient, reliable and scalable server-side applications.

In this article, we will have a look at the Nest library. A library that makes authoring APIs a really nice experience. If you come from the Angular world you will surely recognize yourself with the concepts used, a great CLI and of course great usage of Typescript.

NOTE, it isn't Angular though but pretty darn close, in the best way possible.

This article is part of a series on Nest cause there is no way we could cover everything in one article.

We will cover the following:

  • Why Nest, let's examine the sales pitch as well as mention the features that make Nest a really good choice for your next API
  • Your first CRUD project - covering the Fundamentals, let's scaffold a project and go over the basic constructs

 Why Nest

Let's look at the sales pitch at the homepage

  • Extensible, allow the use of any other library thanks to modular architecture
  • Versatile, an adaptable ecosystem, for all kinds of server-side applications
  • Progressive, takes advantage of latest JavaScript features, design patterns, and mature solutions

Ok, it all sounds great, but give me something I can WOW my colleagues with

It fully supports TypeScript, but use pure JavaScript if you prefer.

It uses the libraries Express and Fastify under the hood, but can also expose their APIs if needed.

Sounds interesting, tell me more

It comes with a CLI, so you can scaffold a project as well as add artifacts.

That's Nice

On top of that, you can easily write unit tests as well as E2E tests with Jest and you can easily build GraphQL APIs with it

Stop it, you are making things up

No really, check it out Nest and GraphQL

Resources

We will mention some great resources throughout this article. Should you miss out on the links we mention, here they are.

Your first project - covering the Fundamentals

Ok then. Let's do this. Before we start to create our first project, we need the CLI to create and run our project and many more things. We can easily install the CLI by using the following command:

npm i -g @nestjs/cli

Next up we need to scaffold a project. So let's do that next:

nest new hello-world

You can replace hello-world with a project name of your choice.

Ok, we got ourselves a lot of files. Judging by the above images we seemed to have gotten a Node.js project with package.json and some testing set up with Jest and of course a bunch of artifacts that seems Nest specific like controller, module and service. Let's have a close look at the scaffolded project:

How does it work?

Before we run the project we just scaffolded, let's first have a closer look so we understand the lifecycle. First off let's look at main.ts. This is the entry point for our app. More specifically it's the bootstrap() method that starts up everything by running the code:

// main.ts

const app = await NestFactory.create(AppModule);
await app.listen(3000);

Ok, so NestFactory calls create() that instantiates the AppModule and we get an app instance that seems to listen on port 3000. Let's go to AppModule and see what happens there:

//app.module.ts

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Ok, we seem to have a class AppModule that is being decorated by @Module decorator that specific a controller AppController and something categorized as a provider AppService.

How does all of this work?

Well, the controller AppController responds to a route request so let's see how that one is set up:

// app.controller.ts

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

The decorator @Get() ensures we map a certain GET request to a certain method on our class. In this case, the default route / will respond with the method getHello() which in turn invokes the appService.getHello(). Let's peek at app.service.ts:

// app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

This seems to be a very simple Class with a method getHello() that returns a string.

Now, let's go back to app.controller.ts.

From what we can see appService is being injected in the constructor of AppController like so:

// excerpt from app.controller.ts

constructor(private readonly appService: AppService) {}

How does it know how to do that?

There are two answers here:

  1. If you add the Injectable() decorator to any service that means that it can be injected in other artifacts, like a controller or a service.
  2. This brings us to the second step. We need to add said service to the providers array for a module to make the DI machinery work.

Oh?

Yea let's try to cement this understanding a bit by going through the motions of adding a new route. But before we do that let's start this project and prove it works like we say it does:

npm start

Now, lets head to the browser:

 Adding a route

We have just learned to scaffold a project and learned to run the same. We think we have a decent grasp on the concepts module, controller and service but nothing will cement this knowledge as much as adding a new route and add all the artifacts we need to make that possible. We will do the following:

We will create a new route /products and to do that, we need to carry out the following steps

  1. Add a new service
  2. Add a new controller and inject our service
  3. Wire up the DI mechanism
  4. Run our application and ensures everything works.

The first thing we are going to do is learn how to work with Nest projects properly. Right now we ran npm start which compiled our TypeScript code and hosted our app at port 3000 but during development, we might want something that listen to changes and compiles automatically. For that lets instead run the command npm run start:dev, which listens to changes and recompiles when needed.

npm run start:dev

NOTE, before we start using the above command let's scaffold all the needed files and then we can run the above for when we are mucking about in specific code files and we want our changes to reflect.

Creating a service

Let's create our products service. For now, make the data static, we can look at adding HTTP calls later. Let's do things the Nest way and use the CLI

nest generate service products

OR the shorter version

nest g s products

Ok, open up the file products/products.service.ts. It should look like so:

import { Injectable } from '@nestjs/common';


@Injectable()
export class ProductsService {}

Now add the method getProducts() so it now looks like so:

import { Injectable } from '@nestjs/common';


@Injectable()
export class ProductsService {
  getProducts() {
    return [{
      id: 1,
      name: 'A SPA app'
    },
    {
      id: 2,
      name: 'A Nest API'
    }]
  }
}

Adding a controller

Time has come to create our controller, so let's do that next. Again we just the CLI, like so:

nest generate controller products

OR, shorter version

nest g co products

Open up products/products.controller:

import { Controller } from '@nestjs/common';

@Controller('products')
export class ProductsController {}

Next step is adding a method getProducts() and ensure we call our service and of course that we don't forget to decorate it with the @Get() decorator.

Your code should now look like this:

import { Controller, Get } from '@nestjs/common';
import { ProductsService } from './products.service';

@Controller('products')
export class ProductsController {
  constructor(private productsService: ProductsService) {}

  @Get()
  getProducts() {
    return this.productsService.getProducts();
  }
}

Let's try this out:

npm run start:dev

Above we can see how our /products route seemed to have been added and that ProductsController will responds to any requests on that route. But how can this be, we haven't done anything to app.module.ts to wire up DI, or have we?

Let's look at app.module.ts:

We can see above that ProductsController and ProductsService have both been added to controllers and providers respectively. The CLI added it for us when we generated the controller and the service.

We almost forgot something which was running our app in the browser, so let's do that:

NOTE, the CLI is powerful it will not only create the necessary files but also do some wire up, but know what you need to do in case you don't use the CLI.

Adding the remaining CRUD routes

Ok, so we've added a route to support /products route. As we all know though we need more routes than that like POST, PUT, DELETE and wildcard route, etc.

How do we add those?

Simple, we just have to create methods for each and everyone and add decorators to support it, like so:

// products.controller.ts

import { Controller, Get, Param, Post, Body, Put, Delete } from '@nestjs/common';
import { ProductsService } from './products.service';

interface ProductDto {
  id: string;
  name: string;
}

@Controller('products')
export class ProductsController {
  constructor(private productsService: ProductsService) {}

  @Get()
  getProducts() {
    return this.productsService.getProducts();
  }

  @Get(':id') 
  getProduct(@Param() params) {
    console.log('get a single product', params.id);
    return this.productsService.getProducts().filter(p => p.id == params.id);
  }

  @Post()
  createProduct(@Body() product: ProductDto) {
    console.log('create product', product);
    this.productsService.createProduct(product);
  }

  @Put()
  updateProduct(@Body() product: ProductDto) {
    console.log('update product', product);
    this.productsService.updateProduct(product);
  }

  @Delete()
  deleteProduct(@Body() product: ProductDto) {
    console.log('delete product', product.id);
    this.productsService.deleteProduct(product.id);
  }
}

and the products.service.ts now looks like this:

import { Injectable } from '@nestjs/common';

@Injectable()
export class ProductsService {
  products = [{
    id: 1,
    name: 'A SPA app'
  },
  {
    id: 2,
    name: 'A Nest API'
  }];

  getProducts() {
    return this.products;
  }

  createProduct(product) {
    this.products = [...this.products, {...product}];
  }

  updateProduct(product) {
    this.products = this.products.map(p => {
      if (p.id == product.id) {
        return { ...product};
      }
      return p;
    });
  }

  deleteProduct(id) {
    this.products = this.products.filter(p => p.id != id);
  }
}

 Summary

Hopefully, you have realized by now how well-structured Nest is and how easy it is to create an API and read query parameters as well as body to support a full CRUD API. We've also introduced the CLI that truly is your best friend in generating the code you need and ensure you don't need to think about how to wire things up.

In our next part we will look at how test our code which is a truly blissful experience. So stay tuned for that.

Discussion

pic
Editor guide
Collapse
kyriakosmichael profile image
Kyriakos Michael

I always had fear of starting with a Typescript First Framework. I gave Nestjs a try, and I'm really happy with the Developer Experience!

Build this API in less than 10 minutes, and I notice .spec.js File are being generated too!
This is a great approach to helping developers writing Test-Driven backends!

Nestjs got my attention!

Collapse
softchris profile image
Chris Noring Author

Yea I have the same experience with Nest, it just works :)

Collapse
stephenradams profile image
Stephen Adams

Nest is looking like the default approach to writing Node applications/APIs with TypeScript. As an Angular developer, Nest and Angular are a match made in heaven.

Collapse
softchris profile image
Chris Noring Author

100% agree. Also there are so many lovely bindings to things like GrahpQL, Swagger, ORMs.. :)

Collapse
stephenradams profile image
Stephen Adams

In the latest episode of TalkScript podcast, they discuss NestJS and also mention TypeORM which sounds interesting.

Collapse
zmotivat0r profile image
Michael Yali

Great article!
Btw, I've created this lib to help with some CRUD scaffolding github.com/nestjsx/crud
Hope that will help people too 😺

Collapse
softchris profile image
Chris Noring Author

thanks Michael. You should try pinging @kammysliwiec or @markpieszak on twitter. I'm sure they would love to hear of a lib making people more efficient :) I'll have a look too of course

Collapse
zmotivat0r profile image
Michael Yali

Yeah, Kamil knows about that lib for sure :) I know he prefers mixins but I made it more powerful and feature rich. He said that he already suggested that lib during one of his workshops, hehe :)
Thanks again for this article. You should definitely add it to this awesome list github.com/juliandavidmr/awesome-n...

Collapse
bergermarko profile image
Marko Berger

Definitely, need to try it. Almost Angular backend;

Collapse
robosek2 profile image
Robert Sęk

Great introduction to NestJS! Thanks for this post :-)

Collapse
kasanielaski profile image
kasanielaski

Great article. In last pic you used map + filter inside updateProduct method. Whould it be better to combine these and use only one reduce call

Collapse
softchris profile image
Chris Noring Author

please show me what you have in mind

Collapse
chechavalerii profile image
Collapse
jcarlosweb profile image
Carlos Campos

In my case i prefer github.com/TypedProject/ts-express..., in which it is not necessary to create modules and has a more flexible configuration

Collapse
maria_michou profile image
Maria Michou

Great article Chris, thanks for sharing!

Collapse
softchris profile image
Chris Noring Author

hi Maria, thank you for that :)

Collapse
dansimiyu profile image
dan-simiyu

Great article. Really enjoyed it. Thanks man.

Collapse
softchris profile image
Chris Noring Author

appreciate that, thank you Dan.

Collapse
softchris profile image
Chris Noring Author

hi Vishnupriya. Are you suggesting that I post on the above blogs for better reach or ?

Collapse
bidipeppercrap profile image
Fransisco Wijaya

Why did you use interface instead of class on DTO?

Collapse
softchris profile image
Chris Noring Author

Hi Fransisco. I tend to use interfaces when all I need is shape, i.e something containing a few properties that belong together. If I can't motivate that I will instantiate it at some point an interface is a good way to start. If a find, later on, I need to create an instance of it then I will switch it to a class

Collapse
tnypxl profile image
tnypxl

This was great. Getting me interested in Typescript too!

Collapse
softchris profile image
Chris Noring Author

Yea IMO it's easier to appreciate TypeScript when it's already set up. Although I do have TypeScript set up here if you are interested in that bit github.com/softchris/typescript-pl...

Collapse
baldassari profile image
Asaph Baldassari

Festify or restify?

Collapse
softchris profile image