Full-stack TypeScript Apps

Hello, Habr! I present to you the translation of the article "Full-Stack TypeScript Apps - Part 1: Developing Backend APIs with Nest.js" by Ana Ribeiro .


Part 1: Developing the server API using Nest.JS


TL; DR: This is a series of articles on how to create a TypeScript web application using Angular and Nest.JS. In the first part, we will write a simple server API using Nest.JS. The second part of this series is devoted to the front-end application using Angular. You can find the final code developed in this article in this GitHub repository.


What is Nest.Js and why Angular?


Nest.js is a framework for building Node.js web server applications.


A distinctive feature is that it solves a problem that no other framework solves: the structure of the node.js. project If you have ever developed under node.js, you know that you can do a lot with one module (for example, Express middleware can do everything from authentication to validation), which, in the end, can lead to an unsupported "mess" . As you will see below, nest.js will help us with this by providing classes that specialize in various problems.


Nest.js is heavily inspired by Angular. For example, both platforms use guards to allow or prevent access to some parts of your applications, and both platforms provide a CanActivate interface for implementing these guards. However, it is important to note that, despite some similar concepts, both structures are independent of each other. That is, in this article, we will create an independent API for our front-end, which can be used with any other framework (React, Vue.JS and so on).


Web application for online orders


In this guide, we will create a simple application in which users can place orders in a restaurant. It will implement this logic:



For simplicity, we will not interact with an external database and do not implement the functionality of our store basket.


Creating the file structure of the Nest.js project


To install Nest.js, we need to install Node.js (v.8.9.x or higher) and NPM. Download and install Node.js for your operating system from the official website (NPM is included). When everything is installed, check the versions:


node -v # v12.11.1 npm -v # 6.11.3 

There are different ways to create a project with Nest.js; they can be found in the documentation . We will use nest-cli . Install it:


npm i -g @nestjs/cli


Next, create our project with a simple command:


nest new nest-restaurant-api


in the process, nest will ask us to choose a package manager: npm or yarn


If everything went well, nest will create the following file structure:


 nest-restaurant-api ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ └── main.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── .gitignore ├── .prettierrc ├── nest-cli.json ├── package.json ├── package-lock.json ├── README.md ├── tsconfig.build.json ├── tsconfig.json └── tslint.json 

go to the created directory and start the development server:


  #    cd nest-restaurant-api #   npm run start:dev 

Open a browser and enter http://localhost:3000 . On the screen we will see:


As part of this guide, we will not be testing our API (although you should write tests for any ready-to-use application). This way you can clear the test directory and delete the src/app.controller.spec.ts (which is the test one). As a result, our source folder contains the following files:



Note: after removing src/app.controller.ts and src/app.module.ts you will not be able to start our application. Don’t worry, we will fix it soon.

Creating entry points (endpoints)



Our API will be available on the /items route. Through this entry point, users can receive data, and administrators manage the menu. Let's create it.


To do this, create a directory called items inside src . All files associated with the /items route will be stored in this new directory.


Creating controllers


in nest.js , as in many other frameworks, controllers are responsible for mapping routes with functionality. To create a controller in nest.js use the nest.js decorator as follows: @Controller(${ENDPOINT}) . Further, in order to map various HTTP methods, such as GET and POST , decorators @Get , @Post , @Delete , etc. are used.


In our case, we need to create a controller that returns dishes available in the restaurant, and which administrators will use to manage the contents of the menu. Let's create a file called items.controller.tc in the src/items directory with the following contents:


  import { Get, Post, Controller } from '@nestjs/common'; @Controller('items') export class ItemsController { @Get() async findAll(): Promise<string[]> { return ['Pizza', 'Coke']; } @Post() async create() { return 'Not yet implemented'; } } 

in order to make our new controller available in our application, register it in the module:


  import { Module } from '@nestjs/common'; import { ItemsController } from './items/items.controller'; @Module({ imports: [], controllers: [ItemsController], providers: [], }) export class AppModule {} 

Launch our application: npm run start:dev and open in the browser http: // localhost: 3000 / items , if you did everything correctly, then we should see the answer to our get request: ['Pizza', 'Coke'] .


Translator's note: to create new controllers, as well as other elements of nest.js : services, providers, etc., it is more convenient to use the nest generate command from the nest-cli . For example, to create the controller described above, you can use the nest generate controller items command, as a result of which nest will create src/items/items.controller.spec.tc and src/items/items.controller.tc following contents:


  import { Get, Post, Controller } from '@nestjs/common'; @Controller('items') export class ItemsController {} 

and register it in app.molule.tc


Adding a Service


Now, when accessing /items our application returns the same array for each request, which we cannot change. Processing and saving data is not the controller's business; for this purpose services are intended in nest.js
Services in nest are @Injectable
The name of the decorator speaks for itself, adding this decorator to the class makes it injectable into other components, such as controllers.
Let's create our service. Create the items.service.ts file in the items.service.ts folder with the following contents:


  import { Injectable } from '@nestjs/common'; @Injectable() export class ItemsService { private readonly items: string[] = ['Pizza', 'Coke']; findAll(): string[] { return this.items; } create(item: string) { this.items.push(item); } } 

and change the ItemsController controller (declared in items.controller.ts ) so that it uses our service:


  import { Get, Post, Body, Controller } from '@nestjs/common'; import { ItemsService } from './items.service'; @Controller('items') export class ItemsController { constructor(private readonly itemsService: ItemsService) {} @Get() async findAll(): Promise<string[]> { return this.itemsService.findAll(); } @Post() async create(@Body() item: string) { this.itemsService.create(item); } } 

in the new version of the controller, we applied the @Body decorator to the create method argument. This argument is used to automatically match the data passed through req.body ['item'] to the argument itself (in this case, item ).
Our controller also receives an instance of the ItemsService class, injected via the constructor. Declaring ItemsService as private readonly makes an instance immutable and visible only inside the class.
And do not forget to register our service in app.module.ts :


  import { Module } from '@nestjs/common'; import { ItemsController } from './items/items.controller'; import { ItemsService } from './items/items.service'; @Module({ imports: [], controllers: [ItemsController], providers: [ItemsService], }) export class AppModule {} 

After all the changes, let's send an HTTP POST request to the menu:


  curl -X POST -H 'content-type: application/json' -d '{"item": "Salad"}' localhost:3000/items 

Then we’ll check whether new dishes appeared on our menu by making a GET request (or by opening http: // localhost: 3000 / items in a browser)


  curl localhost:3000/items 

Creating a Shopping Cart Route


Now that we have the first version of the entry points /items our API, let's implement the shopping cart functionality. The process of creating this functionality differs little from the already created API. Therefore, in order not to clutter up the manual, we will create a component that responds with OK status when accessing.


First, in the ./src/shopping-cart/ folder ./src/shopping-cart/ create the shoping-cart.controller.ts :


  import { Post, Controller } from '@nestjs/common'; @Controller('shopping-cart') export class ShoppingCartController { @Post() async addItem() { return 'This is a fake service :D'; } } 

Register this controller in our module ( app.module.ts ):


  import { Module } from '@nestjs/common'; import { ItemsController } from './items/items.controller'; import { ShoppingCartController } from './shopping-cart/shopping-cart.controller'; import { ItemsService } from './items/items.service'; @Module({ imports: [], controllers: [ItemsController, ShoppingCartController], providers: [ItemsService], }) export class AppModule {} 

To verify this entry point, run the following command, after making sure that the application is running:


  curl -X POST localhost:3000/shopping-cart 

Adding an Interface Typescript for Items


Back to our items service. Now we save only the name of the dish, but this is clearly not enough, and, for sure, we will want to have more information (for example, the cost of the dish). I think you will agree that storing this data as an array of strings is not a good idea?
To solve this problem, we can create an array of objects. But how to maintain the structure of objects? Here the TypeScript interface will help us, in which we define the structure of the items object. Create a new file named item.interface.ts in the src/items folder:


  export interface Items { readonly name: string; readonly price: number; } 

Then items.service.ts file:


 import { Injectable } from '@nestjs/common'; import { Item } from './item.interface'; @Injectable() export class ItemsService { private readonly items: Item[] = []; findAll(): Item[] { return this.items; } create(item: Item) { this.items.push(item); } } 

And also in items.controller.ts :


 import { Get, Post, Body, Controller } from '@nestjs/common'; import { ItemsService } from './items.service'; import { Item } from './item.interface'; @Controller('items') export class ItemsController { constructor(private readonly itemsService: ItemsService) {} @Get() async findAll(): Promise<Item[]> { return this.itemsService.findAll(); } @Post() async create(@Body() item: Item) { this.itemsService.create(item); } } 

Validation of input in Nest.js


Despite the fact that we determined the structure of the item object, our application will not return an error if we send an invalid POST request (any type of data not defined in the interface). For example, for such a request:


  curl -H 'Content-Type: application/json' -d '{ "name": 3, "price": "any" }' http://localhost:3000/items 

the server should respond with a status of 400 (bad request), but instead, our application will respond with a status of 200 (OK).


To solve this problem, create a DTO (Data Transfer Object) and a Pipe component (channel).


DTO is an object that defines how data should be transferred between processes. We describe the DTO in the src/items/create-item.dto.ts :


  import { IsString, IsInt } from 'class-validator'; export class CreateItemDto { @IsString() readonly name: string; @IsInt() readonly price: number; } 

Pipes in Nest.js are the components used for validation. For our API, create a channel in which it checks whether the data sent to the method matches the DTO. One channel can be used by different controllers, so create the src/common/ directory with the file validation.pipe.ts :


  import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform, } from '@nestjs/common'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; @Injectable() export class ValidationPipe implements PipeTransform<any> { async transform(value, metadata: ArgumentMetadata) { const { metatype } = metadata; if (!metatype || !this.toValidate(metatype)) { return value; } const object = plainToClass(metatype, value); const errors = await validate(object); if (errors.length > 0) { throw new BadRequestException('Validation failed'); } return value; } private toValidate(metatype): boolean { const types = [String, Boolean, Number, Array, Object]; return !types.find(type => metatype === type); } } 

Note: We need to install two modules: class-validator and class-transformer . To do this, run npm install class-validator class-transformer in the console and restart the server.

Adapting items.controller.ts for use with our new pipe and DTO:


  import { Get, Post, Body, Controller, UsePipes } from '@nestjs/common'; import { CreateItemDto } from './create-item.dto'; import { ItemsService } from './items.service'; import { Item } from './item.interface'; import { ValidationPipe } from '../common/validation.pipe'; @Controller('items') export class ItemsController { constructor(private readonly itemsService: ItemsService) {} @Get() async findAll(): Promise<Item[]> { return this.itemsService.findAll(); } @Post() @UsePipes(new ValidationPipe()) async create(@Body() createItemDto: CreateItemDto) { this.itemsService.create(createItemDto); } } 

Let's check our code again, now the entry /items accepts data only if they are defined in the DTO. For example:


  curl -H 'Content-Type: application/json' -d '{ "name": "Salad", "price": 3 }' http://localhost:3000/items 

Paste in invalid data (data that cannot be verified in ValidationPipe ), as a result we get the answer:


  {"statusCode":400,"error":"Bad Request","message":"Validation failed"} 

User Authentication with Auth0


during...


Source: https://habr.com/ru/post/471348/


All Articles