The first workplace or how to start developing API on Node.js

Introduction



In this article I would like to share my emotions and acquired skills in developing the first REST API on Node.js using TypeScript, as they say, from scratch. The story is quite banal: “I graduated from the university, received a diploma. Where to go to work? ” As you might have guessed, the problem didn’t pass me by, even though I didn’t have to think too much. The developer (a graduate of the same specialty) called for an internship. I believe that this is a fairly common practice and there are many similar stories. Without thinking twice, I decided to try my hand and went ...



image



The first day. Introducing Node.js



I came to the back-end development. This IT company uses the Node.js platform, which I was completely unfamiliar with. I ran forward a bit, forgetting to tell the reader that I had never developed anything in JavaScript (except for a couple of scripts with copy code). I understood the algorithm of work and the architecture of web applications as a whole, since I developed CRUD in Java, Python and Clojure, but this was not enough. Therefore, the first day I completely devoted to the study of Node.js, this screencast really helped.



While studying the Express web framework, the npm package manager, as well as files such as package.json and tsconfig.json, my head simply went around the amount of information. Another lesson is that mastering all the material at the same time is close to impossible task. By the end of the day, I still managed to configure the environment and was able to run express web server! But it was too early to rejoice, because he went home with a complete sense of misunderstanding. The feeling that I was drowning in the vast world of JS did not leave me for a minute, so a reboot was needed.



Second day. Introducing TypeScript



The same reboot followed on that very day. At this point, I fully recognized my task, we will move on to it a little lower. Knowing that it was not necessary to write in pure JavaScipt, the training from Node.js flowed smoothly to the TypeScript language, namely, its features and syntax. Here I saw the long-awaited types , without which programming was literally 2 days ago, not in functional programming languages. This was my biggest mistake, which prevented me from understanding and learning JavaScript code on the first day.



He previously wrote for the most part in object-oriented programming languages ​​such as Java, C ++, C #. Realizing the possibilities of TypeScript, I felt at ease. This programming language literally breathed into me the life of this complex environment, as it seemed to me at that time. Toward the close of the day I completely set up the environment, launched the server (already on TypeScript), connected the necessary libraries, which I will discuss below. Bottom line: ready to develop the API. We pass directly to the development ...



API Development



An explanation of the principle of work and other explanations of what the REST API is, we will leave, because the forum has a lot of articles about this with examples and development in various programming languages.

image



The task was as follows:



Make a service with a REST API. Authorization by bearer token (/ info, / latency, / logout). Configured CORS for access from any domain. DB - MongoDB. Create a token at each call.



API Description:



  1. / signin [POST] - request for bearer token by id and password // receives data in json
  2. / signup [POST] - registration of a new user: // receives data in json
  3. / info [GET] - returns the user id and id type, requires the token issued by bearer in authentication
  4. / latency [GET] - returns a delay (ping), requires the token issued by bearer in authentication
  5. / logout [GET] - with the parameter all: true - deletes all user bearer tokens or false - deletes only the current bearer token


I note right away, the task looks incredibly simple for a web application developer. But the task must be implemented in a programming language, about which 3 days ago did not know anything at all! Even for me, it looks completely transparent on paper and in Python the implementation took a little time, but I didn’t have such an option. The development stack portended trouble.



Means of implementation



So, I mentioned that on the second day I already studied several libraries (frameworks), we’ll start from this. For routing, I chose routing-controllers , guided by a lot of similarities with decorators from Spring Framework (Java). As the ORM, I chose typeorm , although working with MongoDB in experimental mode, it’s quite enough for such a task. I used uuid to generate tokens, variables are loaded using dotenv .



Web server startup



Usually, express is used in its pure form, but I mentioned the Routing Controllers framework, which allows us to create an express server as follows:



//  Express const app = createExpressServer({ // routePrefix: process.env.SERVER_PREFIX, //  defaults: { nullResultCode: Number(process.env.ERROR_NULL_RESULT_CODE), undefinedResultCode: Number(process.env.ERROR_NULL_UNDEFINED_RESULT_CODE), paramOptions: { required: true } }, //   authorizationChecker: authorizationChecker, // controllers: [UserController] }); //  app.listen(process.env.SERVER_PORT, () => { console.log(process.env.SERVER_MASSAGE); });
      
      







As you can see, there is nothing complicated. In fact, the framework has much more features, but there was no need for them.





DB connection



Earlier, we had already launched the web server, so we will continue to connect to the MongoDB database, having previously deployed it on the local server. Installation and configuration are described in detail in the official documentation . We will directly consider the connection using typeorm:



 //  createConnection({ type: 'mongodb', host: process.env.DB_HOST, database: process.env.DB_NAME_DATABASE, entities: [ User ], synchronize: true, logging: false }).catch(error => console.log(error));
      
      







Everything is quite simple, you need to specify several parameters:







Now we connect server start and connection to a DB. I note that the import of resources is different from the classic one used in Node.js. As a result, we get the following executable file, in my case main.ts:



 import 'reflect-metadata'; import * as dotenv from 'dotenv'; import { createExpressServer } from 'routing-controllers'; import { createConnection } from 'typeorm'; import { authorizationChecker } from './auth/authorizationChecker'; import { UserController } from './controllers/UserController'; import { User } from './models/User'; dotenv.config(); //  createConnection({ type: 'mongodb', host: process.env.DB_HOST, database: process.env.DB_NAME_DATABASE, entities: [ User ], synchronize: true, logging: false }).catch(error => console.log(error)); //  Express const app = createExpressServer({ // routePrefix: process.env.SERVER_PREFIX, //  defaults: { nullResultCode: Number(process.env.ERROR_NULL_RESULT_CODE), undefinedResultCode: Number(process.env.ERROR_NULL_UNDEFINED_RESULT_CODE), paramOptions: { required: true } }, //   authorizationChecker: authorizationChecker, // controllers: [UserController] }); //  app.listen(process.env.SERVER_PORT, () => { console.log(process.env.SERVER_MASSAGE); });
      
      





Entities



Let me remind you that the task is to authenticate and authorize users, respectively, we need an entity: User. But that is not all, since each user has a token and not one! Therefore, it is necessary to create a Token entity.



User



 import { ObjectID } from 'bson'; import { IsEmail, MinLength } from 'class-validator'; import { Column, Entity, ObjectIdColumn } from 'typeorm'; import { Token } from './Token'; //  @Entity() export class User { //  @ObjectIdColumn() id: ObjectID; //Email    @Column() @IsEmail() email: string; //  @Column({ length: 100 }) @MinLength(2) password: string; //  @Column() token: Token; }
      
      





In the User table, we create a field - an array of the very tokens for the user. We also enable calss-validator , as it is necessary that the user logs in via email.



Token



 import { Column, Entity } from 'typeorm'; //   @Entity() export class Token { @Column() accessToken: string; @Column() refreshToken: string; @Column() timeKill: number; }
      
      





The base is as follows:



image



User Authorization



For authorization, we use authorizationChecker (one of the parameters when creating the server, see above), for convenience, we put it in a separate file:



 import { Action, UnauthorizedError } from 'routing-controllers'; import { getMongoRepository } from 'typeorm'; import { User } from '../models/User'; export async function authorizationChecker(action: Action): Promise<boolean> { let token: string; if (action.request.headers.authorization) { //   token = action.request.headers.authorization.split(" ", 2); const repository = getMongoRepository(User); const allUsers = await repository.find(); for (let i = 0; i < allUsers.length; i++) { if (allUsers[i].token.accessToken.toString() === token[1]) { return true; } } } else { throw new UnauthorizedError('This user has not token.'); } return false; }
      
      





After authentication, each user has his own token, so we can get the necessary token from the headers of the response, it looks something like this: Bearer 046a5f60-c55e-11e9-af71-c75526de439e . Now we can check if this token exists, after which the function returns authorization information: true - the user is authorized, false - the user is not authorized. In the application, we can use a very convenient decorator in the controller: @Authorized (). At this point, the authorizationChecker function will be called, which will return a response.



Logics



To begin with, I would like to describe the business logic, since the controller is one line of method calls below the class presented. Also, in the controller we will accept all the data, in our case it will be JSON and Query. We will consider the methods for individual tasks, and at the end we will form the final file, which is called UserService.ts. I note that at that time there was simply not enough knowledge to eliminate dependencies. If you have not met the term dependency injection, I highly recommend reading about it. At the moment I use the DI framework, that is, I use containers, namely injection through constructors. Here, I think, is a good article for review. We return to the task.







Controller



Many do not need to explain what is needed and how the controller is used in the MVC pattern, but I'll still say two words. In short, the controller is the link between the user and the application, which redirects data between them. The logic has been fully described above, the methods of which are called in accordance with the routes, consisting of a URI and an ip server (example: localhost: 3000 / signin) . I mentioned earlier about decorators in the controller: Get , POST , @Authorized and the most important of them is @JsonController. Another very important feature of this framework is that if we want to send and receive JSON, then we use this decorator instead of Controller .



 import * as express from 'express'; import { Authorized, Body, Get, Header, JsonController, NotFoundError, Post, QueryParam, Req, UnauthorizedError } from 'routing-controllers'; import { IPingResult } from '@network-utils/tcp-ping'; import { User } from '../models/User'; import { UserService } from '../services/UserService'; //    JSON @JsonController() export class UserController { userService: UserService //  constructor() { this.userService = new UserService(); } //  @Post('/signin') async login(@Body() user: User): Promise<string> { const responseSignin = await this.userService.userSignin(user); if (responseSignin !== process.env.USER_SERVICE_RESPONSE) { return responseSignin; } else { throw new NotFoundError(process.env.POST_SIGNIN_MASSAGE); } } //  @Post('/signup') async registrateUser(@Body() newUser: User): Promise<string> { const responseSignup = await this.userService.userSignup(newUser); if (responseSignup !== process.env.USER_SERVICE_RESPONSE) { return responseSignup; } else { throw new UnauthorizedError(process.env.POST_SIGNUP_MASSAGE); } } //   @Get('/info') @Authorized() async getId(@Req() req: express.Request): Promise<User> { return this.userService.getUserInfo(req); } //   @Authorized() @Get('/latency') getPing(): Promise<IPingResult> { return this.userService.getLatency(); } @Get('/logout') async deleteToken(@QueryParam("all") all: boolean, @Req() req: express.Request): Promise<void> { this.userService.userLogout(all, req); } }
      
      





Conclusion



In this article, I wanted to reflect no longer the technical component of the correct code or something like that, but simply to share the fact that a person can build a web application using a database and containing at least some logic from an absolute zero in five days. Just think about it, no instrument was familiar, remember yourself or just put it in my place. In no case is this the case that says: "I am the best, you can never do that." On the contrary, this is a cry from the soul of a person who is currently completely delighted with the world of Node.js and shares this with you. And the fact that nothing is impossible, you just need to take and do!



Of course, it cannot be denied that the author knew nothing and sat down to write code for the first time. No, knowledge of OOP, the principles of the REST API, ORM, and the database were present in sufficient quantities. And this can only say that the means of achieving the result absolutely does not play any role and saying in the style: “I won’t go to this work, there is a programming language that I didn’t learn”, for me now it’s just a person’s manifestation not of weakness, but rather protection from an unfamiliar external environment. But what is there to hide, the fear was present with me.



To summarize. I want to advise students and people who have not yet begun their careers in IT, who are not afraid of development tools and unknown technologies. Senior comrades will help you (if you are lucky as well as me), they will explain in detail and answer questions, because each of them was in this position. But do not forget that your desire is the most important aspect!



Link to the project



All Articles