O primeiro local de trabalho ou como começar a desenvolver API no Node.js

1. Introdução


Neste artigo, gostaria de compartilhar minhas emoções e habilidades adquiridas no desenvolvimento da primeira API REST no Node.js usando o TypeScript, como se costuma dizer, do zero. A história é bastante banal: “Me formei na universidade, recebi um diploma. Onde ir trabalhar? Como você deve ter adivinhado, o problema não passou por mim, embora eu não tivesse que pensar muito. O desenvolvedor (um graduado da mesma especialidade) pediu um estágio. Acredito que essa é uma prática bastante comum e há muitas histórias semelhantes. Sem pensar duas vezes, decidi tentar minha mão e fui ...

imagem

Primeiro dia Apresentando o Node.js


Eu vim para o desenvolvimento de back-end. Essa empresa de TI usa a plataforma Node.js. , com a qual eu não estava familiarizado. Avancei um pouco, esquecendo de dizer ao leitor que nunca havia desenvolvido nada em JavaScript (exceto alguns scripts com código de cópia). Em geral, entendi o algoritmo de trabalho e a arquitetura de aplicativos da Web, pois desenvolvi CRUD em Java, Python e Clojure, mas isso não foi suficiente. Portanto, no primeiro dia em que me dediquei completamente ao estudo do Node.js, esse screencast realmente ajudou.

Enquanto estudava a estrutura da Web Express , o gerenciador de pacotes npm , bem como arquivos como package.json e tsconfig.json, minha cabeça simplesmente contornou a quantidade de informações. Outra lição é que dominar todo o material ao mesmo tempo é quase uma tarefa impossível. No final do dia, eu ainda conseguia configurar o ambiente e consegui executar o servidor expresso da Web! Mas era muito cedo para se alegrar, porque ele foi para casa com um sentimento total de mal-entendido. A sensação de que eu estava me afogando no vasto mundo de JS não me deixou por um minuto, então foi necessária uma reinicialização.

Segundo dia. Apresentando o TypeScript


A mesma reinicialização ocorreu no mesmo dia. Neste ponto, eu reconheci completamente o meu problema, passaremos a ele um pouco mais baixo. Sabendo que não era necessário escrever em JavaScipt puro, o treinamento do Node.js fluiu suavemente para a linguagem TypeScript, a saber, seus recursos e sintaxe. Aqui vi os tipos há muito esperados, sem os quais a programação era literalmente 2 dias atrás, não nas linguagens de programação funcionais. Esse foi o meu maior equívoco, o que me impediu de entender e aprender o código escrito em JavaScript no primeiro dia.

Ele escreveu anteriormente em grande parte em linguagens de programação orientadas a objetos, como Java, C ++, C #. Percebendo as possibilidades do TypeScript, me senti à vontade. Essa linguagem de programação literalmente respirou em mim a vida desse ambiente complexo, como me pareceu naquele momento. No final do dia, configurei completamente o ambiente, iniciei o servidor (já no TypeScript) e conectei as bibliotecas necessárias, as quais discutirei abaixo. Conclusão: pronto para desenvolver a API. Passamos diretamente para o desenvolvimento ...

Desenvolvimento de API


Uma explicação do princípio do trabalho e outras explicações sobre o que é a API REST, deixaremos, porque o fórum possui muitos artigos sobre isso com exemplos e desenvolvimento em várias linguagens de programação.
imagem

A tarefa foi a seguinte:

Faça um serviço com uma API REST. Autorização pelo token do portador (/ info, / latency, / logout). CORS configurado para acesso de qualquer domínio. DB - MongoDB. Crie um token em cada chamada.

Descrição da API:

  1. / signin [POST] - solicita ao portador do token por ID e senha // recebe dados em json
  2. / signup [POST] - registro de um novo usuário: // recebe dados em json
  3. / info [GET] - retorna o ID do usuário e o tipo de ID, requer o token emitido pelo portador na autenticação
  4. / latency [GET] - retorna um atraso (ping), requer o token emitido pelo portador na autenticação
  5. / logout [GET] - com o parâmetro all: true - exclui todos os tokens do portador do usuário ou false - exclui apenas o token do portador atual

Percebo imediatamente, a tarefa parece incrivelmente simples para um desenvolvedor de aplicativos da web. Mas a tarefa deve ser implementada em uma linguagem de programação, sobre a qual há 3 dias não sabia nada! Mesmo para mim, parece completamente transparente no papel e, no Python, a implementação demorou um pouco, mas eu não tinha essa opção. A pilha de desenvolvimento pressagiava problemas.

Meios de implementação


Mencionei que no segundo dia eu já estudei várias bibliotecas (frameworks), vamos começar com isso. Para roteamento, escolhi controladores de roteamento , guiados por muitas semelhanças com decoradores do Spring Framework (Java). Como ORM, eu escolhi typeorm , embora trabalhe com o MongoDB no modo experimental, é o suficiente para essa tarefa. Eu usei o uuid para gerar tokens, as variáveis ​​são carregadas usando o dotenv .

Inicialização do servidor da Web


Geralmente, o express é usado em sua forma pura, mas mencionei a estrutura dos controladores de roteamento, que nos permite criar um servidor expresso da seguinte maneira:

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


Como você pode ver, não há nada complicado. De fato, a estrutura possui muito mais recursos, mas não havia necessidade deles.
  • routePrefix é apenas um prefixo no seu URL após o endereço do servidor, por exemplo: localhost : 3000 / prefix
  • padrões - nada de interessante, basta inicializar os códigos de erro
  • notificationChecker - uma ótima oportunidade para a estrutura verificar a autorização do usuário; consideraremos com mais detalhes
  • controllers é um dos principais campos em que especificamos os controladores usados ​​em nossa aplicação


Conexão de banco de dados


Anteriormente, já havíamos lançado o servidor da Web, portanto, continuaremos a nos conectar ao banco de dados do MongoDB, depois de implantá-lo no servidor local. A instalação e configuração são descritas em detalhes na documentação oficial . Vamos considerar diretamente a conexão usando 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)); 


Tudo é bastante simples, você precisa especificar vários parâmetros:

  • type - DB
  • host - endereço IP em que você implantou o banco de dados
  • database - o nome do banco de dados que foi criado anteriormente no mongodb
  • sincronizar - sincronização automática com o banco de dados (Nota: era difícil dominar a migração naquele momento)
  • entidades - aqui indicamos as entidades com as quais a sincronização é realizada


Agora, conectamos o lançamento do servidor e a conexão ao banco de dados. Observo que a importação de recursos é diferente da clássica usada no Node.js. Como resultado, obtemos o seguinte arquivo executável, no meu caso 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); }); 

Entidades


Deixe-me lembrá-lo de que a tarefa é autenticar e autorizar usuários, respectivamente, precisamos de uma entidade: Usuário. Mas isso não é tudo, já que cada usuário tem um token e não um! Portanto, é necessário criar uma entidade Token.

Usuário

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

Na tabela Usuário, criamos um campo - uma matriz dos mesmos tokens para o usuário. Também habilitamos o calss-validator , pois é necessário que o usuário efetue login por email.

Token

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

A base é a seguinte:

imagem

Autorização do Usuário


Para autorização, usamos o authorChecker (um dos parâmetros ao criar o servidor, veja acima); por conveniência, colocamos em um arquivo separado:

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

Após a autenticação, cada usuário possui seu próprio token, para que possamos obter o token necessário dos cabeçalhos da resposta, é algo como: Portador 046a5f60-c55e-11e9-af71-c75526de439e . Agora podemos verificar se esse token existe, após o qual a função retorna informações de autorização: true - o usuário está autorizado, false - o usuário não está autorizado. No aplicativo, podemos usar um decorador muito conveniente no controlador: @Authorized (). Nesse momento, a função authenticationChecker será chamada, o que retornará uma resposta.

Lógica


Para começar, gostaria de descrever a lógica de negócios, uma vez que o controlador é uma linha de chamada de método abaixo da classe apresentada. Além disso, no controlador, aceitaremos todos os dados; no nosso caso, serão JSON e Query. Consideraremos os métodos para tarefas individuais e, no final, formaremos o arquivo final, chamado UserService.ts. Observo que naquele momento simplesmente não havia conhecimento suficiente para eliminar dependências. Se você não encontrou o termo injeção de dependência, recomendo a leitura. No momento, eu uso a estrutura DI, ou seja, uso contêineres, ou seja, injeção através de construtores. Aqui, eu acho, é um bom artigo para revisão. Voltamos à tarefa.

  • / signin [POST] - autenticação do usuário registrado. Tudo é muito simples e transparente. Só precisamos encontrar esse usuário no banco de dados e emitir um novo token. Para leitura e escrita, o MongoRepository é usado.

     async userSignin(user: User): Promise<string> { // Mongo repository const repo = getMongoRepository(User); //       let userEmail = await repo.findOne({ email: user.email, password: user.password }); if (userEmail) { //  userEmail = await this.setToken(userEmail); //    repo.save(userEmail); return userEmail.token.accessToken; } return process.env.USER_SERVICE_RESPONSE; } 
  • / signup [POST] - registra um novo usuário. Um método muito semelhante, pois, inicialmente, também estamos procurando um usuário para que não tenhamos usuários registrados com um e-mail. Em seguida, escrevemos o novo usuário no banco de dados, depois de emitir o token.

     async userSignup(newUser: User): Promise<string> { // Mongo repository const repo = getMongoRepository(User); //   email (   2    email) const userRepeat = await repo.findOne({ email: newUser.email }); if (!userRepeat) { //  newUser = await this.setToken(newUser); //   const addUser = getMongoManager(); await addUser.save(newUser); return newUser.token.accessToken; } else { return process.env.USER_SERVICE_RESPONSE; } } 
  • / info [GET] - retorna a identificação do usuário e o tipo de identificação, requer o token emitido pelo portador na autenticação. A imagem também é transparente: primeiro obtemos o token atual do usuário nos cabeçalhos da solicitação, depois procuramos no banco de dados e determinamos em quem ele se encontra, e retornamos o usuário encontrado.

     async getUserInfo(req: express.Request): Promise<User> { // Mongo repository const repository = getMongoRepository(User); //    const user = await this.findUser(req, repository); return user; } private async findUser(req: express.Request, repository: MongoRepository<User>): Promise<User> { if (req.get(process.env.HEADER_AUTH)) { //  const token = req.get(process.env.HEADER_AUTH).split(' ', 2); //    const usersAll = await repository.find(); //  for (let i = 0; i < usersAll.length; i++) { if (usersAll[i].token.accessToken.toString() === token[1]) { return usersAll[i]; } } } } 

  • / latency [GET] - retorna um atraso (ping), requer o token emitido pelo portador na autenticação. Um parágrafo completamente desinteressante do artigo, no entanto. Aqui eu usei apenas uma biblioteca pronta para verificar o atraso do tcp-ping.

     getLatency(): Promise<IPingResult> { function update(progress: number, total: number): void { console.log(progress, '/', total); } const latency = ping({ address: process.env.PING_ADRESS, attempts: Number(process.env.PING_ATTEMPTS), port: Number(process.env.PING_PORT), timeout: Number(process.env.PING_TIMEOUT) }, update).then(result => { console.log('ping result:', result); return result; }); return latency; } 
  • / logout [GET] - com o parâmetro all: true - exclui todos os tokens do portador do usuário ou false - exclui apenas o token do portador atual. Só precisamos encontrar o usuário, verificar o parâmetro de consulta e remover os tokens. Eu acho que tudo deve ficar claro.

     async userLogout(all: boolean, req: express.Request): Promise<void> { // Mongo repository const repository = getMongoRepository(User); //    const user = await this.findUser(req, repository); if (all) { // true    user.token.accessToken = process.env.GET_LOGOUT_TOKEN; user.token.refreshToken = process.env.GET_LOGOUT_TOKEN; //  repository.save(user); } else { // false    user.token.accessToken = process.env.GET_LOGOUT_TOKEN; //  repository.save(user); } } 


Controlador


Muitos não precisam explicar o que é necessário e como o controlador é usado no padrão MVC, mas ainda direi duas palavras. Em resumo, o controlador é o link entre o usuário e o aplicativo que redireciona os dados entre eles. A lógica foi completamente descrita acima, cujos métodos são chamados de acordo com as rotas, consistindo em um URI e um servidor IP (exemplo: localhost: 3000 / signin) . Eu mencionei anteriormente sobre os decoradores no controlador: Get , POST , @Authorized e o mais importante deles é o @JsonController. Outra característica muito importante dessa estrutura é que, se queremos enviar e receber JSON, usamos esse decorador em vez do 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); } } 

Conclusão


Neste artigo, eu queria refletir não mais o componente técnico do código correto ou algo parecido, mas simplesmente compartilhar o fato de que uma pessoa pode criar um aplicativo Web usando um banco de dados e contendo pelo menos alguma lógica de um zero absoluto em cinco dias. Apenas pense, nenhum instrumento era familiar, lembre-se de si mesmo ou apenas coloque-o no meu lugar. Em nenhum caso é o caso que diz: "Eu sou o melhor, você nunca pode fazer isso". Pelo contrário, este é um grito da alma de uma pessoa que atualmente está completamente encantada com o mundo do Node.js e compartilha isso com você. E o fato de que nada é impossível, você só precisa pegar e fazer!

Obviamente, não se pode negar que o autor não sabia nada e sentou-se para escrever o código pela primeira vez. Não, o conhecimento de OOP, os princípios da API REST, ORM e o banco de dados estavam presentes em quantidades suficientes. E isso só pode dizer que os meios para alcançar o resultado absolutamente não desempenham nenhum papel e dizer no estilo: "Eu não vou para este trabalho, há uma linguagem de programação que eu não aprendi", para mim agora é apenas a manifestação de uma pessoa, não de fraqueza, mas sim proteção contra um ambiente externo desconhecido. Mas o que há para esconder, o medo estava presente comigo.

Para resumir. Quero aconselhar estudantes e pessoas que ainda não começaram sua carreira em TI, a não terem medo de ferramentas de desenvolvimento e tecnologias desconhecidas. Os camaradas seniores certamente o ajudarão (se você tiver sorte, assim como eu), eles explicarão em detalhes e responderão a perguntas, porque cada um deles estava nessa posição. Mas não esqueça que seu desejo é o aspecto mais importante!

Link para o projeto

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


All Articles