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

Chris Noring - Jun 17 '19 - - Dev Community

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


Enter fullscreen mode Exit fullscreen mode

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



nest new hello-world


Enter fullscreen mode Exit fullscreen mode

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);


Enter fullscreen mode Exit fullscreen mode

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 {}


Enter fullscreen mode Exit fullscreen mode

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();
  }
}


Enter fullscreen mode Exit fullscreen mode

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!';
  }
}


Enter fullscreen mode Exit fullscreen mode

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) {}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

OR the shorter version



nest g s products


Enter fullscreen mode Exit fullscreen mode

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



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


@Injectable()
export class ProductsService {}


Enter fullscreen mode Exit fullscreen mode

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'
    }]
  }
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

OR, shorter version



nest g co products


Enter fullscreen mode Exit fullscreen mode

Open up products/products.controller:



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

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


Enter fullscreen mode Exit fullscreen mode

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();
  }
}


Enter fullscreen mode Exit fullscreen mode

Let's try this out:



npm run start:dev


Enter fullscreen mode Exit fullscreen mode

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);
  }
}


Enter fullscreen mode Exit fullscreen mode

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);
}
}

Enter fullscreen mode Exit fullscreen mode




 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player