A simple Calculator API built with Express and TypeScript. This API provides basic arithmetic operations and includes Swagger documentation.
-
Parte 1: Criando uma API Simples com Express e TypeScript
- Passo 1: Configuração Inicial
- Passo 2: Instalar Dependências
- Passo 3: Configurar o TypeScript
- Passo 4: Estrutura do Projeto
- Passo 5: Criar o Servidor Express
- Passo 6: Configurar Scripts no
package.json
- Passo 7: Compilar e Executar o Projeto
- Passo 8: Configurar o Deployment no Vercel
- Conclusão da Parte 1
-
Parte 2: Adicionando Documentação Swagger à API com Express e TypeScript
-
Crie um novo diretório para o seu projeto e navegue até ele:
mkdir calculator-api cd calculator-api
-
Inicialize um novo projeto Node.js:
npm init -y
-
Instale as dependências necessárias:
npm install express dotenv
express
: Framework para criar servidores web.dotenv
: Carrega variáveis de ambiente a partir de um arquivo.env
.
-
Instale as dependências de desenvolvimento:
npm install --save-dev typescript ts-node @types/node @types/express nodemon rimraf copyfiles
typescript
: Linguagem de programação que adiciona tipagem estática ao JavaScript.ts-node
: Executa arquivos TypeScript diretamente.@types/node
e@types/express
: Tipos TypeScript para Node.js e Express.nodemon
: Ferramenta que reinicia automaticamente o servidor ao detectar mudanças nos arquivos.rimraf
: Ferramenta para deletar diretórios (útil para limpar a pasta de build).copyfiles
: Ferramenta para copiar arquivos durante o processo de build.
-
Inicialize a configuração do TypeScript:
npx tsc --init
-
Edite o arquivo
tsconfig.json
para configurar o compilador TypeScript:{ "compilerOptions": { "target": "ES6", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "resolveJsonModule": true }, "include": ["src/**/*"] }
-
Crie a estrutura de diretórios do projeto:
mkdir src touch src/index.ts .env .gitignore
-
Configure o arquivo
.gitignore
para excluir arquivos desnecessários:node_modules dist .env
-
Configure o arquivo
.env
com a porta do servidor:PORT=4000
-
Edite o arquivo
src/index.ts
para configurar o servidor Express:import express, { Request, Response } from 'express'; import dotenv from 'dotenv'; dotenv.config(); const app = express(); app.use(express.json()); const PORT = process.env.PORT || 4000; app.get('/', (req: Request, res: Response) => { res.send('Welcome to the Calculator API!'); }); app.post('/add', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } const result = a + b; res.json({ result }); }); app.post('/subtract', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } const result = a - b; res.json({ result }); }); app.post('/multiply', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } const result = a * b; res.json({ result }); }); app.post('/divide', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } if (b === 0) { return res.status(400).send('Division by zero is not allowed.'); } const result = a / b; res.json({ result }); }); app.listen(PORT, () => { console.log(`Server ready on port ${PORT}.`); }); export default app;
-
Edite o arquivo
package.json
para incluir os scripts:{ "name": "calculator-api", "version": "1.0.0", "description": "", "main": "index.ts", "scripts": { "clean": "rimraf dist", "build": "npm run clean && tsc", "dev": "npm run build && nodemon dist/index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "dotenv": "^16.4.5", "express": "^4.19.2" }, "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^20.12.12", "nodemon": "^3.1.0", "rimraf": "^5.0.7", "typescript": "^5.4.5", "copyfiles": "^2.4.1" } }
clean
: Exclui a pastadist
antes de cada build.build
: Compila o projeto TypeScript.dev
: Compila o projeto e inicia o servidor usandonodemon
.
-
Compile o projeto:
npm run build
-
Execute o servidor de desenvolvimento:
npm run dev
-
Instale a CLI do Vercel se ainda não tiver:
npm install -g vercel
-
Faça login na Vercel:
vercel login
-
Crie um arquivo
vercel.json
na raiz do projeto:{ "version": 2, "builds": [ { "src": "dist/index.js", "use": "@vercel/node" } ], "routes": [ { "src": "/(.*)", "dest": "/dist/index.js" } ] }
builds
: Define como construir o projeto para Vercel.routes
: Define as rot
as e destinos para os arquivos construídos.
-
Implante o projeto na Vercel:
vercel --prod
Nota: O comando
vercel --prod
garante que o projeto seja implantado na produção. O deployment via PR pode não refletir corretamente.
-
Instale as dependências necessárias para o Swagger:
npm install swagger-ui-express swagger-jsdoc
-
Instale as dependências de desenvolvimento para os tipos do Swagger:
npm install --save-dev @types/swagger-jsdoc @types/swagger-ui-express
-
Edite o arquivo
src/index.ts
para configurar o Swagger:import express, { Request, Response } from 'express'; import dotenv from 'dotenv'; import swaggerUi from 'swagger-ui-express'; import swaggerJsDoc from 'swagger-jsdoc'; dotenv.config(); const app = express(); app.use(express.json()); const PORT = process.env.PORT || 4000; const swaggerOptions = { swaggerDefinition: { openapi: '3.0.0', info: { title: 'Calculator API', version: '1.0.0', description: 'A simple API for arithmetic operations' }, servers: [ { url: 'https://calculator-api-pi.vercel.app/', description: 'Production server' }, { url: `http://localhost:${PORT}`, description: 'Local server' } ] }, apis: ['./dist/**/*.js'] }; const swaggerDocs = swaggerJsDoc(swaggerOptions); const options: swaggerUi.SwaggerUiOptions = { customCss: '.swagger-ui .opblock .opblock-summary-path-description-wrapper { align-items: center; display: flex; flex-wrap: wrap; gap: 0 10px; padding: 0 10px; width: 100%; }', customCssUrl: 'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.3.0/swagger-ui.min.css', }; app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs, options)); app.get('/', (req: Request, res: Response) => { res.redirect('/api-docs'); }); /** * @swagger * /add: * post: * summary: Add two numbers * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * a: * type: number * b: * type: number * responses: * 200: * description: The result of the addition * content: * application/json: * schema: * type: object * properties: * result: * type: number */ app.post('/add', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } const result = a + b; res.json({ result }); }); /** * @swagger * /subtract: * post: * summary: Subtract two numbers * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * a: * type: number * b: * type: number * responses: * 200: * description: The result of the subtraction * content: * application/json: * schema: * type: object * properties: * result: * type: number */ app.post('/subtract', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } const result = a - b; res.json({ result }); }); /** * @swagger * /multiply: * post: * summary: Multiply two numbers * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * a: * type: number * b: * type: number * responses: * 200: * description: The result of the multiplication * content: * application/json: * schema: * type: object * properties: * result: * type: number */ app.post('/multiply', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } const result = a * b; res.json({ result }); }); /** * @swagger * /divide: * post: * summary: Divide two numbers * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * a: * type: number * b: * type: number * responses: * 200: * description: The result of the division * content: * application/json: * schema: * type: object * properties: * result: * type: number */ app.post('/divide', (req: Request, res: Response) => { const { a, b } = req.body; if (typeof a !== 'number' || typeof b !== 'number') { return res.status(400).send('Both a and b should be numbers.'); } if (b === 0) { return res.status(400).send('Division by zero is not allowed.'); } const result = a / b; res.json({ result }); }); app.listen(PORT, () => { console.log(`Server ready on port ${PORT}.`); }); export default app;
Para solucionar o problema com o Swagger na Vercel, configuramos as opções do Swagger UI:
const options: swaggerUi.SwaggerUiOptions = {
customCss: '.swagger-ui .opblock .opblock-summary-path-description-wrapper { align-items: center; display: flex; flex-wrap: wrap; gap: 0 10px; padding: 0 10px; width: 100%; }',
customCssUrl: 'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.3.0/swagger-ui.min.css',
};
customCss
: Estilos personalizados para corrigir o problema de alinhamento dos endpoints no Swagger UI quando hospedado na Vercel.customCssUrl
: Usamos uma URL CDN para carregar o CSS da versão 4.3.0 do Swagger UI, garantindo a compatibilidade e corrigindo possíveis problemas de estilo.
-
Compile o projeto:
npm run build
-
Execute o servidor de desenvolvimento:
npm run dev
-
Reimplante o projeto na Vercel:
vercel --prod