Pular para o conteúdo
Bruno Dórea
Todos os posts

9 mins de leitura


API Node.js + Express (Parte 1)

Criação de uma API Node.js + Express


Este artigo foi criado como uma anotação do projeto desenvolvido no curso da Formação Node.js da DIO.

O repositório completo do projeto pode ser encontrado aqui no GitHub.

Criar uma API é uma tarefa essencial para qualquer desenvolvedor backend. Neste artigo, veremos como criar uma API utilizando Node.js e Express. Vamos construir uma API para gerenciar dados de jogadores de futebol na "Champions League".

Configuração Inicial

Primeiramente, precisamos configurar nosso ambiente de desenvolvimento. Vamos iniciar criando um projeto Node.js.

1. Inicializando o Projeto

Primeiramente, vamos criar uma nova pasta para o projeto e inicializá-lo com o comando npm init -y. Isso criará um arquivo package.json com as configurações básicas do projeto.

mkdir api-champions-league
cd api-champions-league
npm init -y

2. Instalando Dependências

Em seguida, precisamos instalar as dependências necessárias para o projeto. Vamos usar o Express para criar a API, o Dotenv para gerenciar variáveis de ambiente e o Cors para habilitar o compartilhamento de recursos entre diferentes origens. Além disso, instalaremos TypeScript e algumas ferramentas de desenvolvimento.

npm install express dotenv cors
npm install --save-dev typescript @types/node @types/express @types/cors tsup tsx

3. Configurando TypeScript

Vamos inicializar o TypeScript no projeto. Isso criará um arquivo tsconfig.json com a configuração padrão.

npx tsc --init

Edite o arquivo tsconfig.json para incluir as configurações recomendadas para projetos Node.js:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  }
}

4. Atualizando o package.json

Adicione os scripts de build e execução ao package.json:

{
  "scripts": {
    "dist": "tsup src",
    "start:dev": "tsx src/server.ts",
    "start:watch": "tsx watch src/server.ts",
    "start:dist": "npm run dist && node dist/src/index.js"
  },
}

Com essas configurações, estamos prontos para começar a desenvolver nossa API. Nos próximos passos, criaremos os controladores, serviços, repositórios e demais arquivos necessários para a funcionalidade completa da aplicação.

5. Criando o .env

Precisamos criar também um arquivo .env na raiz do projeto, onde vamos especificar a porta que será utilizada pela aplicação

PORT=3333

6. Estrutura de Diretórios

Vamos organizar nossa aplicação em várias camadas para manter o código modular e fácil de manter. Aqui está a estrutura de diretórios:

| src/
├── controllers/
   ├── playersController.ts
├── models/
   ├── playerModel.ts
   └── httpResponseModel.ts
├── repositories/
   └── playersRepository.ts
├── services/
   └── playersService.ts
├── utils/
   └── httpHelper.ts
├── routes.ts
├── app.ts
└── server.ts
| .env
| package.json
| tsconfig.json

Configurando o Servidor

server.ts

O arquivo server.ts é o ponto de entrada da nossa aplicação. Ele configura e inicia o servidor:

import dotenv from 'dotenv'
import createApp from './app'
 
dotenv.config()
 
const app = createApp()
const port = process.env.PORT
 
app.listen(port, () => {
  console.log(`🔥 Server running at port http://localhost:${port}`)
})

app.ts

O arquivo app.ts cria e configura a instância do Express:

import express, { Request, Response } from 'express';
import router from './routes';
 
function createApp() {
  const app = express();
 
  app.use(express.json());
  app.use("/api/v1", router);
 
  return app;
}
 
export default createApp;

routes.ts

Este arquivo define as rotas da aplicação:

import { Router } from "express"
import * as PlayerController from "./controllers/playersController"
 
const router = Router()
 
router.get('/players', PlayerController.getPlayer)
router.post('/players', PlayerController.createPlayer)
router.get('/players/:id', PlayerController.getPlayerById)
router.delete('/players/:id', PlayerController.deletePlayer)
router.patch('/players/:id', PlayerController.updatePlayer)
 
export default router

Controllers

Os controladores lidam com as requisições HTTP e chamam os serviços apropriados.

playersController.ts

import { Request, Response } from "express"
import * as service from "../services/playersService"
import { StatisticsModel } from "../models/statisticsModel";
 
export const getPlayer = async (req: Request, res: Response) => {
  const httpResponse = await service.getPlayerService()
  res.status(httpResponse.statusCode).json(httpResponse.body)
}
 
export const getPlayerById = async (req: Request, res: Response) => {
  const id = parseInt(req.params.id)
  const httpResponse = await service.getPlayerByIdService(id)
  res.status(httpResponse.statusCode).json(httpResponse.body)
}
 
export const createPlayer = async (req: Request, res: Response) => {
  const bodyValue = req.body
  const httpResponse = await service.createPlayerService(bodyValue)
  res.status(httpResponse.statusCode).json(httpResponse.body)
}
 
export const deletePlayer = async (req: Request, res: Response) => {
  const id = parseInt(req.params.id)
  const httpResponse = await service.deletePlayerService(id)
  res.status(httpResponse.statusCode).json(httpResponse.body)
}
 
export const updatePlayer = async (req: Request, res: Response) => {
  const id = parseInt(req.params.id)
  const bodyValue: StatisticsModel = req.body
  const httpResponse = await service.updatePlayerService(id, bodyValue)
  res.status(httpResponse.statusCode).json(httpResponse.body)
}

Services

Os serviços contêm a lógica de negócios da aplicação.

playersService.ts

import { PlayerModel } from "../models/playerModel"
import { StatisticsModel } from "../models/statisticsModel"
import * as PlayerRepository from "../repositories/playersRepository"
import * as HttpResponse from "../utils/httpHelper"
 
export const getPlayerService = async () => {
  const data = await PlayerRepository.findAllPlayers()
  let response = null
 
  if (data) {
    response = await HttpResponse.ok(data)
  } else {
    response = await HttpResponse.noContent()
  }
 
  return response
}
 
export const getPlayerByIdService = async (id: number) => {
  const data = await PlayerRepository.findPlayersById(id)
  let response = null
 
  if (data) {
    response = await HttpResponse.ok(data)
  } else {
    response = await HttpResponse.noContent()
  }
 
  return response
}
 
export const createPlayerService = async (player: PlayerModel) => {
  let response = null
 
  if (Object.keys(player).length !== 0) {
    await PlayerRepository.insertPlayer(player)
    response = await HttpResponse.created()
  } else {
    response = await HttpResponse.badRequest()
  }
 
  return response
}
 
export const deletePlayerService = async (id: number) => {
  let response = null
  const isDeleted = await PlayerRepository.deleteOnePlayer(id)
 
  if (isDeleted) {
    response = await HttpResponse.ok({ message: "deleted" })
  } else {
    response = await HttpResponse.badRequest()
  }
 
  return response
}
 
export const updatePlayerService = async (id: number, statistics: StatisticsModel) => {
  const data = await PlayerRepository.findAndModifyPlayer(id, statistics)
  let response = null
 
  if (Object.keys(data).length === 0) {
    response = await HttpResponse.badRequest();
  } else {
    response = await HttpResponse.ok(data);
  }
 
  return response
}

Repositories

Os repositórios lidam com a interação com o banco de dados ou outras formas de armazenamento de dados.

playersRepository.ts

import { PlayerModel } from "../models/playerModel"
import { StatisticsModel } from "../models/statisticsModel";
 
const db: PlayerModel[] = [
  // Dados fictícios de jogadores
  {
    id: 1,
    name: "Lionel Messi",
    club: "Inter Miami",
    nationality: "Argentina",
    position: "Forward",
    statistics: {
      Overall: 93,
      Pace: 85,
      Shooting: 94,
      Passing: 91,
      Dribbling: 95,
      Defending: 38,
      Physical: 65,
    },
  },
];
 
export const findAllPlayers = async (): Promise<PlayerModel[]> => {
  return db
}
 
export const findPlayersById = async (id: number): Promise<PlayerModel | undefined> => {
  return db.find(player => player.id === id)
}
 
export const insertPlayer = async (player: PlayerModel) => {
  db.push(player)
}
 
export const deleteOnePlayer = async (id: number) => {
  const index = db.findIndex(player => player.id === id)
  if (index !== -1) {
    db.splice(index, 1)
    return true
  }
 
  return false
}
 
export const findAndModifyPlayer = async (id: number, statistics: StatisticsModel): Promise<PlayerModel> => {
  const playerIndex = db.findIndex(player => player.id === id)
  if (playerIndex !== -1) {
    db[playerIndex].statistics = statistics
  }
 
  return db[playerIndex]
}

Utils

httpHelper.ts

Utilitários para criar respostas HTTP.

import { HttpResponse } from "../models/httpResponseModel"
 
export const ok = async (data: any): Promise<HttpResponse> => {
  return {
    statusCode: 200,
    body: data,
  }
}
 
export const created = async (): Promise<HttpResponse> => {
  return {
    statusCode: 201,
    body: {
      message: "successful"
    },
  }
}
 
export const noContent = async (): Promise<HttpResponse> => {
  return {
    statusCode: 204,
    body: null,
  }
}
 
export const badRequest = async (): Promise<HttpResponse> => {
  return {
    statusCode: 400,
    body: null,
  }
}

Conclusão

Criar uma API com Node.js e Express pode ser simplificado através de uma boa organização de código e modularização. Este exemplo mostra como estruturar uma aplicação completa, desde a configuração inicial até a implementação das rotas, controladores, serviços e repositórios. Isso facilita a manutenção e a escalabilidade da aplicação.

Além disso, está em desenvolvimento a geração da documentação da API utilizando o Swagger UI. Este é um aprimoramento pessoal e não faz parte do conteúdo do curso.

A parte 2 com a implementação do Swagger UI já está disponível aqui.