[Tutorial] - Como criar um boilerplate para projetos Node.js
Depois de já ter configurado o Next.js Boilerplate e o Vite Boilerplate, agora vamos criar um boilerplate
para API's
em Node.js
Passo a Passo
Inicializar o Projeto
Acesse o diretório desejado e execute os comandos abaixo:
mkdir node-boilerplate
cd node-boilerplate
npm init -y
git init
Dessa forma, foi criado a pasta do projeto, inicializado o git
e criado o arquivo package.json
, onde vamos deixar no seguinte molde:
{
"name": "node-boilerplate",
"version": "1.0.0",
"description": "Boilerplate para projetos Node.js",
"repository": "https://github.com/diasjoaovitor/node-boilerplate.git",
"author": "João Vitor <[email protected]>",
"license": "MIT",
"main": "src/app.ts",
"keywords": [
"node-boilerplate",
"eslint",
"commit-linter",
"prettier",
"jest",
"typescript"
]
}
Especificar a versão do Node:
Crie o arquivo .nvmrc
e adicione a versão a ser utilizada:
lts/iron
É obrigatório deixar uma linha em branco ao final do arquivo para que essa configuração funcione corretamente. Dessa forma, ao acessar o diretório do projeto e executar o comando nvm use
, o NVM
carrega automaticamente a versão especificada no arquivo.
Ensino como instalar o NVM e versões especificas do Node
no artigo Como configurar um ambiente de desenvolvimento com o WSL
Configurar o commit linter
Instale o git-commit-msg-linter:
npm i -D git-commit-msg-linter
Essa biblioteca verifica se a mensagem de um commit
contém um prefixo semântico como feat
, fix
, refactor
e demais convenções.
Crie o arquivo .gitignore
e adicione a pasta node_modules
Configurar o TypeScript
npm i -D typescript ts-node-dev @types/node
Crie o arquivo tsconfig.json
{
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "CommonJS",
"skipLibCheck": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"strict": true,
"outDir": "dist",
"removeComments": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"baseUrl": "src"
},
"include": ["src"]
}
Estrutura do arquivo tsconfig.json
:
-
composite: true
: Habilita o modo de projeto composto. Isso permite que o TypeScript otimize a compilação em projetos com dependências entre subprojetos. Por exemplo, se você tem um projeto com vários subdiretórios, o modo composto permite que o TypeScript recompile apenas os arquivos afetados em vez de todo o projeto. -
target: "ESNext"
: Define o alvo da transpilação para uma versão mais recente do JavaScript, conhecida como ESNext. -
module: "CommonJS"
: Especifica que o sistema de módulos usado será o CommonJS, que é o padrão para o Node.js. O CommonJS permite a utilização derequire
emodule.exports
para módulos. -
skipLibCheck: true
: Faz com que o compilador ignore a checagem de tipos nas definições de bibliotecas (.d.ts
). -
moduleResolution: "node"
: Define a estratégia de resolução de módulos comonode
, o que significa que o TypeScript usará o mesmo mecanismo de resolução de módulos que o Node.js. -
allowSyntheticDefaultImports: true
: Permite que você importe módulos que não têm uma exportação padrão. -
esModuleInterop: true
: Habilita a interoperabilidade completa entre os módulos ES e CommonJS, corrigindo problemas com importações padrão e nomes de exportação. -
strict: true
: Ativa um conjunto de opções de verificação estrita de tipos no TypeScript. -
outDir: "dist"
: Define o diretório de saída onde os arquivos JavaScript compilados serão colocados. Neste caso, todos os arquivos transpilados serão colocados no diretóriodist
. -
removeComments: true
: Remove os comentários dos arquivos.ts
ao transpilá-los para.js
, o que resulta em arquivos mais leves. -
forceConsistentCasingInFileNames: true
: Impede que o compilador permita importações que usam diferentes capitalizações em nomes de arquivos. -
sourceMap: true
: Gera arquivos que mapeiam o código transpilado para o código original TypeScript. -
baseUrl: "src"
: Define o diretório base para resolução de módulos relativos. -
include: ["src"]
: Especifica que apenas os arquivos incluídos emsrc
devem ser incluídos no processo de compilação.
Variáveis de Ambiente
As informações sensíveis devem ser atribuídas em arquivos .env
, dessa forma vamos instalar o dotenv:
npm i dotenv
Crie um arquivo .env
e um arquivo .env.example
. Esse segundo arquivo serve para indicar quais variáveis de ambiente devem ser atribuídas, pois o arquivo .env
não deve ser subir no repositório, dessa forma, vamos especificar essa condição no arquivo .gitignore
.
.env
:
MY_SECRET_KEY=12345678
.env.example
:
MY_SECRET_KEY=
.gitignore
:
.env
Finalmente, vamos criar o arquivo src/app.ts
e atribuir a configuração:
import dotenv from 'dotenv'
dotenv.config()
console.log(process.env.MY_SECRET_KEY)
Configurar Alias
Instale o module-alias:
npm i module-alias
npm i -D @types/module-alias
Crie o arquivo src/config/alias-config.ts
:
import { addAlias } from 'module-alias'
import { resolve } from 'path'
const env = process.env.NODE_ENV
const basePath = env === 'production' ? 'dist/src' : 'src'
addAlias('@', resolve(basePath))
Adicione o path no tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["*"]
}
}
}
Agora vamos criar os arquivos sum.ts
e index.ts
no diretório src/utils
:
export const sum = (a: number, b: number) => a + b
export * from './sum'
Importe a configuração no arquivo app.ts
, dessa forma é possível importar a função sum
com o atalho @/utils
:
import './config/alias-config'
import dotenv from 'dotenv'
import { sum } from '@/utils'
dotenv.config()
console.log(process.env.PORT)
console.log(sum(1, 2))
Configurar o Jest
Instale as bibliotecas:
npm i -D jest ts-jest @types/jest
Crie os arquivos jest.config.ts
e jest.setup.ts
:
export default {
preset: 'ts-jest',
testRegex: '((\\.|/*.)(test))\\.ts?$',
modulePaths: ['<rootDir>/src/'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
}
}
beforeAll(async () => {
jest.spyOn(console, 'error').mockImplementation(() => {})
})
afterAll(async () => {
jest.restoreAllMocks()
})
Crie o arquivos src/utils/sum.ts
e src/tests/sum.test.ts
:
export const sum = (a: number, b: number) => a + b
import { sum } from '.'
describe('Sum', () => {
it('Sum a and b', () => {
const result = sum(2, 3)
expect(result).toBe(5)
})
})
Configurar as regras do editor e do código
Instale o módulo e a extensão do prettier:
npm i -D prettier
Crie o arquivo .prettierrc.json
:
{
"trailingComma": "none",
"semi": false,
"singleQuote": true
}
"trailingComma": "none"
: Isso indica que o Prettier não deve adicionar vírgulas ao final de listas, objetos ou parâmetros de função quando eles são formatados em várias linhas."semi": false
: Isso significa que o Prettier não deve adicionar ponto e vírgula ao final de cada instrução."singleQuote": true
: Isso especifica que o Prettier deve usar aspas simples em vez de aspas duplas, sempre que possível.
Defina o prettier
como o formatador padrão do VSCode em Default Formatter
e habilite a opção Format On Save
Crie a pasta .vscode
e inclua o arquivo settings.json
:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.autoSave": "off",
"git.autofetch": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
"editor.formatOnSave": true
- Esta opção faz com que o VSCode formate automaticamente o código quando você salva um arquivo. Isso ajuda a manter o código limpo e consistente com as regras de formatação definidas."editor.defaultFormatter": "esbenp.prettier-vscode"
- Define o Prettier como o formatador de código padrão. O Prettier é uma ferramenta popular que suporta muitas linguagens e estilos de codificação."files.autoSave": "off"
- Desativa o salvamento automático de arquivos. Com essa configuração, os arquivos não serão salvos automaticamente após um período ou quando o foco é alterado; você precisará salvar manualmente suas alterações."git.autofetch": true
- Quando habilitado, o VSCode buscará automaticamente as alterações mais recentes do seu repositório Git periodicamente. Isso é útil para manter seu repositório local atualizado com as alterações remotas."typescript.tsdk": "node_modules/typescript/lib"
: Garante que o VSCode use a versão do TypeScript instalada nas dependências do projeto ao invés da versão global.
Crie o arquivo .editorconfig
:
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
-
root = true
: Esta configuração sinaliza que este é o arquivo de configuração principal e que o EditorConfig não deve procurar por outros arquivos de configuração nas pastas acima. -
[*]
: Este é um padrão de correspondência que se aplica a todos os arquivos no projeto. -
indent_style = space
: Define que o estilo de indentação deve ser feito com espaços em vez de tabulações. -
indent_size = 2
: Especifica que o tamanho da indentação deve ser de dois espaços. -
end_of_line = lf
: Indica que o final de linha deve ser formatado usando LF (Line Feed), que é o padrão para sistemas Unix e macOS. -
charset = utf-8
: Define que o conjunto de caracteres do arquivo deve ser UTF-8. -
trim_trailing_whitespace = true
: Quando verdadeiro, remove automaticamente qualquer espaço em branco no final das linhas ao salvar o arquivo. -
insert_final_newline = true
: Garante que haja uma nova linha no final do arquivo ao salvar.
Configurar Linting
O linting é o processo de aplicar regras a uma base de código e destacar padrões ou códigos problemáticos que não aderem a determinadas diretrizes de estilo. ESLint permite que os desenvolvedores descubram problemas com seu código sem a necessidade de executá-lo.
Instale o eslint, @typescript-eslint/eslint-plugin, @typescript-eslint/parser e o eslint-config-prettier:
npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier
Crie o arquivo .eslintrc.cjs
:
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
ignorePatterns: ['dist', '.eslintrc.cjs', 'node_modules'],
parser: '@typescript-eslint/parser'
}
Automatizar Scripts Pré-Commit
Instale o lint-staged e o husky
npm i -D lint-staged husky
Crie o arquivo .lintstagedrc.cjs
:
const path = require('path')
const buildCommand = (filenames) => {
const files = filenames.map((f) => path.relative(process.cwd(), f))
return [
`npx prettier --write ${files.join(' --file ')}`,
`npx eslint --fix ${files.join(' ')} --report-unused-disable-directives --max-warnings 0`,
`npx jest --runInBand --findRelatedTests ${filenames.join(' ')} --passWithNoTests`
]
}
module.exports = {
'*.{js,ts}': [buildCommand]
}
Inicie as configurações do husky
:
npx husky init
Dessa forma, foi criado o script prepare
no arquivo package.json
e a pasta .husky
, onde vamos alterar o conteúdo do arquivo pre-commit
para:
npx --no-install lint-staged
Configurar Scripts:
Adicione os scripts no arquivos package.json
:
{
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/app.ts",
"start": "NODE_ENV=production node dist/src/app.js",
"build": "tsc",
"test": "jest --runInBand",
"test:watch": "jest --runInBand --watchAll",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
"lint": "eslint . --ext js,ts --report-unused-disable-directives --max-warnings 0",
"prepare": "husky"
}
}
Configurar Integração Contínua
Crie o arquivo .github/workflows/ci.yml
:
name: ci
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 'lts/iron'
cache: 'npm'
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ${{ runner.tool_cache }}/npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
- name: Linting
run: npm run lint
- name: Testing
run: npm run test
- name: Clean build folder
run: rm -rf dist
- name: Build
run: npm run build
Considerações Finais
Ao final desses passos, temos um boilerplate
genérico para projetos Node.js
e que possui as principais ferramentas de desenvolvimento para manter o código seguro e organizado.
O repositório está disponível no meu perfil do GitHub.