portaldacalheta.pt
  • Principal
  • Ágil
  • Américas
  • Processos Financeiros
  • Design Ux
Processo Interno

Construindo uma API REST Node.js / TypeScript, Parte 1: Express.js



Como faço para escrever uma API REST em Node.js?

Ao construir um back-end para uma API REST, Express.js é frequentemente a primeira escolha entre os frameworks Node.js. Embora também suporte a construção de HTML estático e modelos, nesta série, vamos nos concentrar no desenvolvimento de back-end usando TypeScript. A API REST resultante será aquela que qualquer estrutura de front-end ou serviço de back-end externo seria capaz de consultar.

Você precisará:



  • Conhecimento básico de JavaScript e TypeScript
  • Conhecimento básico de Node.js
  • Conhecimento básico da arquitetura REST (cf. esta seção do meu artigo anterior da API REST, se necessário)
  • Uma instalação pronta do Node.js (de preferência versão 14+)

Em um terminal (ou prompt de comando), criaremos uma pasta para o projeto. Nessa pasta, execute npm init. Isso vai criar alguns dos arquivos básicos de projeto Node.js de que precisamos.



A seguir, adicionaremos a estrutura Express.js e algumas bibliotecas úteis:



npm install --save express debug winston express-winston cors

Existem boas razões para essas bibliotecas serem Desenvolvedor Node.js favoritos:

  • debug é um módulo que usaremos para evitar a chamada de console.log() ao desenvolver nosso aplicativo. Dessa forma, podemos filtrar facilmente as instruções de depuração durante a solução de problemas. Eles também podem ser totalmente desligados na produção, em vez de serem removidos manualmente.
  • winston é responsável por registrar solicitações à nossa API e pelas respostas (e erros) retornadas. express-winston integra-se diretamente com Express.js, de modo que todos os padrões relacionados à API winston código de registro já está feito.
  • cors é um middleware Express.js que nos permite habilitar compartilhamento de recursos de origem cruzada . Sem isso, nossa API só poderia ser usada em front-ends servidos exatamente no mesmo subdomínio de nosso back-end.

Nosso back end usa esses pacotes quando está em execução. Mas também precisamos instalar alguns desenvolvimento dependências para nossa configuração TypeScript. Para isso, vamos executar:



npm install --save-dev @types/cors @types/express @types/debug source-map-support tslint typescript

Essas dependências são necessárias para habilitar o TypeScript para o código do nosso aplicativo, junto com os tipos usados ​​por Express.js e outras dependências. Isso pode economizar muito tempo quando estamos usando um IDE como WebStorm ou VSCode, permitindo-nos completar alguns métodos de função automaticamente durante a codificação.

As dependências finais em package.json deve ser assim:



o que pode causar um vazamento de memória
'dependencies': { 'debug': '^4.2.0', 'express': '^4.17.1', 'express-winston': '^4.0.5', 'winston': '^3.3.3', 'cors': '^2.8.5' }, 'devDependencies': { '@types/cors': '^2.8.7', '@types/debug': '^4.1.5', '@types/express': '^4.17.2', 'source-map-support': '^0.5.16', 'tslint': '^6.0.0', 'typescript': '^3.7.5' }

Agora que temos todas as dependências necessárias instaladas, vamos começar a construir nosso próprio código!

Estrutura do projeto da API REST TypeScript

Para este tutorial, vamos criar apenas três arquivos:



  1. ./app.ts
  2. ./common/common.routes.config.ts
  3. ./users/users.routes.config.ts

A ideia por trás das duas pastas da estrutura do projeto (common e users) é ter módulos individuais com suas próprias responsabilidades. Nesse sentido, eventualmente teremos alguns ou todos os itens a seguir para cada módulo:

  • Configuração de rota para definir as solicitações que nossa API pode manipular
  • Serviços para tarefas como conectar-se a nossos modelos de banco de dados, fazer consultas ou conectar-se a serviços externos que são exigidos pela solicitação específica
  • Middleware para executar validações de solicitações específicas antes que o controlador final de uma rota trate de suas especificidades
  • Modelos para definir modelos de dados correspondentes a um determinado esquema de banco de dados, para facilitar o armazenamento e recuperação de dados
  • Controladores para separar a configuração de rota do código que finalmente (após qualquer middleware) processa uma solicitação de rota, chama as funções de serviço acima, se necessário, e dá uma resposta ao cliente

Essa estrutura de pastas fornece um ponto de partida precoce para o restante desta série de tutoriais e o suficiente para começar a praticar.



Um arquivo de rotas comuns no TypeScript

No common , vamos criar a pasta common.routes.config.ts arquivo parecido com o seguinte:

import express from 'express'; export class CommonRoutesConfig { app: express.Application; name: string; constructor(app: express.Application, name: string) { this.app = app; this.name = name; } getName() { return this.name; } }

A forma como estamos criando as rotas aqui é opcional. Mas, como estamos trabalhando com TypeScript, nosso cenário de rotas é uma oportunidade para praticar o uso de herança com o extends palavra-chave, como veremos em breve. Neste projeto, todos os arquivos de rota têm o mesmo comportamento: Eles têm um nome (que usaremos para fins de depuração) e acesso ao Express.js principal Application objeto.



Agora, podemos começar a criar o arquivo de rota de usuários. No users pasta, vamos criar users.routes.config.ts e comece a codificá-lo assim:

import {CommonRoutesConfig} from '../common/common.routes.config'; import express from 'express'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, 'UsersRoutes'); } }

Aqui, estamos importando o CommonRoutesConfig classe e estendendo-a para nossa nova classe, chamada UsersRoutes. Com o construtor, enviamos o aplicativo (o objeto principal express.Application) e o nome UsersRoutes para o construtor CommonRoutesConfig.

Este exemplo é bastante simples, mas ao dimensionar para criar vários arquivos de rota, isso nos ajudará a evitar código duplicado.

Suponha que queiramos adicionar novos recursos a este arquivo, como o registro. Poderíamos adicionar o campo necessário ao CommonRoutesConfig classe e, em seguida, todas as rotas que estendem CommonRoutesConfig terá acesso a ele.

Uso de funções abstratas do TypeScript para funcionalidade semelhante em classes

E se quisermos ter alguma funcionalidade que seja semelhante entre essas classes (como configurar os terminais da API), mas isso precisa de uma implementação diferente para cada classe? Uma opção é usar um recurso TypeScript chamado abstração .

Vamos criar uma função abstrata muito simples que UsersRoutes classe (e futuras classes de roteamento) herdarão de CommonRoutesConfig. Digamos que queremos forçar todas as rotas a ter uma função (para que possamos chamá-la de nosso construtor comum) chamada configureRoutes(). É onde declararemos os endpoints de cada recurso da classe de roteamento.

Para fazer isso, vamos adicionar três coisas rápidas a common.routes.config.ts:

  1. A palavra-chave abstract para o nosso class linha, para permitir a abstração para esta classe.
  2. Uma nova declaração de função no final de nossa classe, abstract configureRoutes(): express.Application;. Isso força qualquer classe estendendo CommonRoutesConfig para fornecer uma implementação que corresponda a essa assinatura - se não, o compilador do TypeScript gerará um erro.
  3. Uma chamada para this.configureRoutes(); no final do construtor, pois agora podemos ter certeza de que esta função existirá.

O resultado:

import express from 'express'; export abstract class CommonRoutesConfig { app: express.Application; name: string; constructor(app: express.Application, name: string) { this.app = app; this.name = name; this.configureRoutes(); } getName() { return this.name; } abstract configureRoutes(): express.Application; }

Com isso, qualquer classe que estenda CommonRoutesConfig deve ter uma função chamada configureRoutes() que retorna um express.Application objeto. Isso significa users.routes.config.ts precisa de atualização:

import {CommonRoutesConfig} from '../common/common.routes.config'; import express from 'express'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, 'UsersRoutes'); } configureRoutes() { // (we'll add the actual route configuration here next) return this.app; } }

Para recapitular o que fizemos:

Estamos primeiro importando o common.routes.config arquivo, então o express módulo. Em seguida, definimos o UserRoutes classe, dizendo que queremos estender o CommonRoutesConfig classe base, o que implica que prometemos que ela implementará configureRoutes().

Para enviar informações junto com CommonRoutesConfig , estamos usando a classe constructor da classe. Ele espera receber o express.Application objeto, que descreveremos em maior profundidade na próxima etapa. Com super(), passamos para o construtor de CommonRoutesConfig o aplicativo e o nome de nossas rotas, que neste cenário é UsersRoutes. (super(), por sua vez, chamará nossa implementação de configureRoutes().)

Configurando as Rotas Express.js dos Endpoints dos Usuários

O configureRoutes() é onde criaremos os terminais para usuários de nossa API REST. Lá, vamos usar o inscrição e os seus rota funcionalidades do Express.js.

A ideia de usar o app.route() função é evitar a duplicação de código, o que é fácil, pois estamos criando uma API REST com recursos bem definidos. O principal recurso para este tutorial é Comercial . Temos dois casos neste cenário:

  • Quando o chamador da API deseja criar um novo usuário ou listar todos os usuários existentes, o terminal deve inicialmente ter apenas users no final do caminho solicitado. (Não entraremos em filtragem de consulta, paginação ou outras consultas semelhantes neste artigo.)
  • Quando o chamador deseja fazer algo específico para um registro de usuário específico, o caminho do recurso da solicitação seguirá o padrão users/:userId.

O caminho .route() funciona em Express.js nos permite lidar com verbos HTTP com algum encadeamento elegante. Isso ocorre porque .get(), .post(), etc., todos retornam a mesma instância de IRoute que o primeiro .route() ligar faz. A configuração final será assim:

configureRoutes() { this.app.route(`/users`) .get((req: express.Request, res: express.Response) => { res.status(200).send(`List of users`); }) .post((req: express.Request, res: express.Response) => { res.status(200).send(`Post to users`); }); this.app.route(`/users/:userId`) .all((req: express.Request, res: express.Response, next: express.NextFunction) => { // this middleware function runs before any request to /users/:userId // but it doesn't accomplish anything just yet--- // it simply passes control to the next applicable function below using next() next(); }) .get((req: express.Request, res: express.Response) => { res.status(200).send(`GET requested for id ${req.params.userId}`); }) .put((req: express.Request, res: express.Response) => { res.status(200).send(`PUT requested for id ${req.params.userId}`); }) .patch((req: express.Request, res: express.Response) => { res.status(200).send(`PATCH requested for id ${req.params.userId}`); }) .delete((req: express.Request, res: express.Response) => { res.status(200).send(`DELETE requested for id ${req.params.userId}`); }); return this.app; }

O código acima permite que qualquer cliente REST API chame nosso users ponto final com um POST ou um GET solicitação. Da mesma forma, permite que um cliente ligue para o nosso /users/:userId ponto final com um GET, PUT, PATCH ou DELETE solicitação.

Mas para /users/:userId, também adicionamos middleware genérico usando o all() , que será executada antes de get(), put(), patch() ou delete() funções. Esta função será benéfica quando (posteriormente na série) criarmos rotas que devem ser acessadas apenas por usuários autenticados.

Você deve ter notado que em nosso .all() função — como acontece com qualquer parte de middleware — temos três tipos de campos: Request, Response e NextFunction.

  • o Solicitação é a maneira como Express.js representa a solicitação HTTP a ser tratada. Este tipo atualiza e estende o Node.js nativo tipo de solicitação.
  • o Resposta é da mesma forma que Express.js representa a resposta HTTP, novamente estendendo o Node.js nativo tipo de resposta.
  • Não menos importante, o NextFunction serve como uma função de retorno de chamada, permitindo que o controle passe por quaisquer outras funções de middleware. Ao longo do caminho, todo middleware compartilhará os mesmos objetos de solicitação e resposta antes que o controlador finalmente envie uma resposta de volta ao solicitante.

Nosso arquivo de ponto de entrada Node.js, app.ts

Agora que configuramos alguns esqueletos de rota básicos, vamos começar a configurar o ponto de entrada do aplicativo. Vamos criar o app.ts arquivo na raiz da pasta do nosso projeto e comece com este código:

import express from 'express'; import * as http from 'http'; import * as bodyparser from 'body-parser'; import * as winston from 'winston'; import * as expressWinston from 'express-winston'; import cors from 'cors'; import {CommonRoutesConfig} from './common/common.routes.config'; import {UsersRoutes} from './users/users.routes.config'; import debug from 'debug';

Apenas duas dessas importações são novas neste ponto do artigo:

  • http é um módulo nativo do Node.js. É necessário iniciar nosso aplicativo Express.js.
  • body-parser é um middleware que vem com Express.js. Ele analisa a solicitação (em nosso caso, como JSON) antes que o controle vá para nossos próprios manipuladores de solicitação.

Agora que importamos os arquivos, vamos começar a declarar as variáveis ​​que queremos usar:

diferença entre llc s corp e c corp
const app: express.Application = express(); const server: http.Server = http.createServer(app); const port: Number = 3000; const routes: Array = []; const debugLog: debug.IDebugger = debug('app');

O express() função retorna o objeto de aplicativo Express.js principal que passaremos por todo o nosso código, começando por adicioná-lo ao http.Server objeto. (Precisamos iniciar o http.Server após configurar nosso express.Application.)

Ouviremos na porta 3000 em vez das portas padrão 80 (HTTP) ou 443 (HTTPS) porque elas normalmente seriam usadas para o front-end de um aplicativo.

Por que a porta 3000?

Não há regra segundo a qual a porta deve ser 3000 - se não for especificada, uma porta arbitrária será atribuído, mas 3000 é usado em todos os exemplos de documentação para Node.js e Express.js, portanto, continuamos a tradição aqui.

O Node.js pode compartilhar portas com o front end?

Ainda podemos executar localmente em uma porta personalizada, mesmo quando queremos que nosso back-end responda às solicitações em portas padrão. Isso exigiria um proxy reverso para receber solicitações na porta 80 ou 443 com um domínio ou subdomínio específico. Em seguida, ele os redirecionaria para nossa porta interna 3000.

O routes array controlará nossos arquivos de rotas para fins de depuração, como veremos a seguir.

Finalmente, debugLog acabará como uma função semelhante a console.log, mas melhor: é mais fácil de ajustar porque é automaticamente definido para tudo o que quisermos chamar nosso contexto de arquivo / módulo. (Neste caso, o chamamos de 'app' quando passamos isso em uma string para o debug() construtor.)

Agora, estamos prontos para configurar todos os nossos módulos de middleware Express.js e as rotas de nossa API:

// here we are adding middleware to parse all incoming requests as JSON app.use(bodyparser.json()); // here we are adding middleware to allow cross-origin requests app.use(cors()); // here we are configuring the expressWinston logging middleware, // which will automatically log all HTTP requests handled by Express.js app.use(expressWinston.logger({ transports: [ new winston.transports.Console() ], format: winston.format.combine( winston.format.colorize(), winston.format.json() ) })); // here we are adding the UserRoutes to our array, // after sending the Express.js application object to have the routes added to our app! routes.push(new UsersRoutes(app)); // here we are configuring the expressWinston error-logging middleware, // which doesn't *handle* errors per se, but does *log* them app.use(expressWinston.errorLogger({ transports: [ new winston.transports.Console() ], format: winston.format.combine( winston.format.colorize(), winston.format.json() ) })); // this is a simple route to make sure everything is working properly app.get('/', (req: express.Request, res: express.Response) => { res.status(200).send(`Server up and running!`) });

Você deve ter notado que expressWinston.errorLogger está definido depois de nós definimos nossas rotas. Isso não é um erro! Enquanto o documentação express-winston afirma:

O logger precisa ser adicionado APÓS o roteador expresso (app.router) e ANTES de qualquer um de seus manipuladores de erro personalizados (express.handler).

Finalmente e mais importante:

server.listen(port, () => { debugLog(`Server running at http://localhost:${port}`); routes.forEach((route: CommonRoutesConfig) => { debugLog(`Routes configured for ${route.getName()}`); }); });

Isso realmente inicia nosso servidor. Assim que for iniciado, o Node.js executará nossa função de retorno de chamada, que relata que estamos executando, seguida pelos nomes de todas as rotas que configuramos - até agora, apenas UsersRoutes.

Atualizando package.json para Transpilar TypeScript para JavaScript e executar o aplicativo

Agora que temos nosso esqueleto pronto para ser executado, primeiro precisamos de alguma configuração padrão para habilitar a transpilação do TypeScript. Vamos adicionar o arquivo tsconfig.json na raiz do projeto:

{ 'compilerOptions': { 'target': 'es2016', 'module': 'commonjs', 'outDir': './dist', 'strict': true, 'esModuleInterop': true, 'inlineSourceMap': true } }

Então, só precisamos adicionar os toques finais a package.json na forma dos seguintes scripts:

'scripts': { 'start': 'tsc && node ./dist/app.js', 'debug': 'export DEBUG=* && npm run start', 'test': 'echo 'Error: no test specified' && exit 1' },

O test script é um marcador que substituiremos posteriormente na série.

o tsc no start o script pertence ao TypeScript. É responsável por transpilar nosso código TypeScript em JavaScript, que será gerado em dist pasta. Então, apenas rodamos a versão construída com node ./dist/app.js.

O debug script chama o start script, mas primeiro define um DEBUG variável de ambiente. Isso tem o efeito de habilitar todos os nossos debugLog() instruções (mais outras semelhantes do próprio Express.js, que usa o mesmo debug módulo que usamos) para enviar detalhes úteis para o terminal - detalhes que estão (convenientemente) ocultos ao executar o servidor no modo de produção com um npm start.

Tente executar npm run debug você mesmo, e depois, compare com npm start para ver como a saída do console muda.

Dica: Você pode limitar a saída de depuração para nosso app.ts do próprio arquivo debugLog() declarações usando DEBUG=app em vez de DEBUG=*. O debug módulo geralmente é bastante flexível, e este recurso não é exceção .

Os usuários do Windows provavelmente precisarão alterar o export para SET desde export é assim que funciona no Mac e no Linux. Se o seu projeto precisa oferecer suporte a vários ambientes de desenvolvimento, o pacote cross-env fornece uma solução simples aqui.

Testando o back-end do Live Express.js

Com npm run debug ou npm start ainda em andamento, nossa API REST estará pronta para solicitações de serviço na porta 3000. Neste ponto, podemos usar cURL, Carteiro , Insônia , etc. para testar o backend.

Como criamos apenas um esqueleto para o recurso de usuários, podemos simplesmente enviar solicitações sem corpo para ver se tudo está funcionando conforme o esperado. Por exemplo:

curl --location --request GET 'localhost:3000/users/12345'

Nosso backend deve enviar de volta a resposta GET requested for id 12345.

Quanto a POST ing:

curl --location --request POST 'localhost:3000/users' --data-raw ''

Este e todos os outros tipos de solicitações para as quais construímos esqueletos serão bastante semelhantes.

Preparado para desenvolvimento rápido de API REST em Node.js com TypeScript

Neste artigo, começamos a criar uma API REST configurando o projeto do zero e mergulhando nos fundamentos da estrutura Express.js. Em seguida, demos nosso primeiro passo para dominar o TypeScript, criando um padrão com UsersRoutesConfig estendendo CommonRoutesConfig, um padrão que reutilizaremos no próximo artigo desta série. Terminamos configurando nosso app.ts ponto de entrada para usar nossas novas rotas e package.json com scripts para construir e executar nosso aplicativo.

Mas mesmo os fundamentos de uma API REST feita com Express.js e TypeScript são bastante envolventes. Dentro a próxima parte desta série, nos concentramos na criação de controladores adequados para os recursos dos usuários e nos aprofundamos em alguns padrões úteis para serviços, middleware, controladores e modelos.

O projeto completo está disponível no GitHub , e o código no final deste artigo é encontrado no toptal-article-01 ramo.

Compreender o básico

Posso usar TypeScript com Node.js?

Absolutamente! É muito comum que pacotes npm populares (incluindo Express.js) tenham arquivos de definição de tipo TypeScript correspondentes. Isso é verdade sobre o próprio Node.js, além de subcomponentes incluídos como seu pacote de depuração.

O Node.js é bom para APIs REST?

Sim. O Node.js pode ser usado sozinho para criar APIs REST prontas para produção, e também existem várias estruturas populares como Express.js para reduzir o boilerplate inevitável.

O TypeScript é difícil de aprender?

Não, não é difícil começar a aprender TypeScript para quem tem um background moderno de JavaScript. É ainda mais fácil para quem tem experiência em programação orientada a objetos. Mas dominar todas as nuances e práticas recomendadas do TypeScript leva tempo, como acontece com qualquer habilidade.

Devo usar o TypeScript?

Depende do projeto, mas é definitivamente recomendado para programação em Node.js. É uma linguagem mais expressiva para modelar domínios de problemas do mundo real no back end. Isso torna o código mais legível e reduz o potencial de bugs.

Para que é usado o TypeScript?

O TypeScript é usado em qualquer lugar que o JavaScript seja encontrado, mas é especialmente adequado para aplicativos maiores. Ele usa JavaScript como base, adicionando tipagem estática e um suporte muito melhor para o paradigma de programação orientada a objetos (OOP). Isso, por sua vez, oferece suporte a uma experiência mais avançada de desenvolvimento e depuração.

Lei Glass-Steagall: sua revogação causou a crise financeira?

Investidores E Financiamento

Lei Glass-Steagall: sua revogação causou a crise financeira?
Tiro em Las Vegas: ISIS assume a responsabilidade; Trump chama isso de 'um ato de pura maldade'

Tiro em Las Vegas: ISIS assume a responsabilidade; Trump chama isso de 'um ato de pura maldade'

Mundo

Publicações Populares
As crianças estão passando mais 'tempo sozinhos' com os pais. O que isso significa?
As crianças estão passando mais 'tempo sozinhos' com os pais. O que isso significa?
Um tutorial de fluxo de trabalho de design para desenvolvedores: entregue melhor UI / UX no prazo
Um tutorial de fluxo de trabalho de design para desenvolvedores: entregue melhor UI / UX no prazo
Aprimore o fluxo do usuário - um guia para análise de UX
Aprimore o fluxo do usuário - um guia para análise de UX
Trabalhos a serem realizados: Transforme as necessidades do cliente em soluções de produtos
Trabalhos a serem realizados: Transforme as necessidades do cliente em soluções de produtos
O presidente dos EUA, Donald Trump, critica a ex-NSA Susan Rice por desmascarar funcionários
O presidente dos EUA, Donald Trump, critica a ex-NSA Susan Rice por desmascarar funcionários
 
Todos Juntos Agora - Uma Visão Geral do Design Inclusivo
Todos Juntos Agora - Uma Visão Geral do Design Inclusivo
Métodos de pesquisa UX e o caminho para a empatia do usuário
Métodos de pesquisa UX e o caminho para a empatia do usuário
Como o GWT desbloqueia a realidade aumentada em seu navegador
Como o GWT desbloqueia a realidade aumentada em seu navegador
Um guia para codificação UTF-8 em PHP e MySQL
Um guia para codificação UTF-8 em PHP e MySQL
O que Force Touch significa para IU e UX?
O que Force Touch significa para IU e UX?
Publicações Populares
  • usar raspberry pi como servidor
  • para que o node js é usado
  • rendimento de dendê por hectare
  • javascript converter data para utc
  • prática recomendada de modelagem financeira excel
  • como avaliar uma empresa farmacêutica
  • tamanhos de tela de mídia para design responsivo
Categorias
  • Ágil
  • Américas
  • Processos Financeiros
  • Design Ux
  • © 2022 | Todos Os Direitos Reservados

    portaldacalheta.pt