/nodejs-api-express

Criação de uma Api Rest com NodeJs e Express

Primary LanguageJavaScript

Criando uma Api REST com NodeJS e Express

Criando a base do projeto

Crie uma pasta como o nome do projeto (mkdir nome-projeto)

Entre na pasta e inicie o Git

git init

Criar o arquivo .gitignore (touch .gitignore)

node_modules
.env

Execute os comandos abaixos (você precisará do Yarn)

yarn init -y
yarn add express
yarn add sucrase nodemon -D

Crie o arquivo src/routes.js

import { Router } from "express";

const routes = new Router();

routes.get("/", (req, res) => {
  return res.json({ status: "on-line" });
});

export default routes;

Crie o arquivo src/app.js

import express from "express";
import routes from "./routes";

class App {
  constructor() {
    this.server = express();

    this.middlewares();
    this.routes();
  }

  middlewares() {
    this.server.use(express.json());
  }

  routes() {
    this.server.use(routes);
  }
}

export default new App().server;

Crie o arquivo src/server.js

import app from "./app";

app.listen(3333);

Crie o arquivo nodemon.json

{
  "execMap": {
    "js": "sucrase-node"
  }
}

Acresente no arquivo package.json

  "scripts": {
    "dev": "nodemon src/server.js"
  }

Variáveis ambiente

Execute o comando

yarn add dotenv

Crie o arquivo .env

APP_URL= http://localhost:3333
NODE_ENV=development

Inclua no arquivo src/app.js

import 'dotenv/config';

Padronização de Projetos (ESLint, Prettier & EditorConfig)

Execute os comandos abaixo

yarn add eslint -D
yarn eslint --init

Selecione

  • To check syntax, find problems, and enforce code style
  • JavaScript modules (import/export)
  • None of these
  • Marque somente Node
  • Use a popular style guide
  • Airbnb (https://github.com/airbnb/javascript)
  • Javascript
  • Y

Ao terminar, exclua o arquivo package-lock.json e execute os comandos

yarn
yarn add prettier eslint-config-prettier eslint-plugin-prettier -D

Crie o arquivo .prettierrc

{
  "singleQuote": true,
  "trailingComma": "es5"
}

Altere o arquivo .eslintrc.js

module.exports = {
  env: {
    es6: true,
    node: true,
  },
  extends: ['airbnb-base', 'prettier'],
  plugins: ['prettier'],
  globals: {
    Atomics: 'readonly',
    SharedArrayBuffer: 'readonly',
  },
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  rules: {
    'prettier/prettier': 'error',
    'class-methods-use-this': 'off',
    'no-param-reassign': 'off',
    camelcase: 'off',
    'no-unused-vars': ['error', { argsIgnorePattern: 'next' }],
  },
};

No VSCode, clique com botão direito e selecionar a opção generate .editorconfig

root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

Para corrigir todos arquivos (indentação)

 yarn eslint --fix src --ext .js

Tratamento de exceções

Execute os comandos

yarn add youch
yarn add express-async-errors

Iremos utilizar também o Sentry

Faça login com sua conta, crie um projeto e siga tutorial.

Exemplo

yarn add @sentry/node@5.5.0

Adcione ao arquivo .env

# Sentry

SENTRY_DSN='https://6b1a0c46525042f491a188aba36f68be@sentry.io/1511984'

A url é mesma que mostra no seu projeto no Sentry. Exemplo: Sentry.init({ dsn: 'https://6b1a0c46525042f491a188aba36f68be@sentry.io/1511984' });

Crie o arquivo src/config/sentry.js

export default {
  dsn: process.env.SENRTY_DSN,
};

Inclua no arquivo src/app.js

import Youch from 'youch';

import * as Sentry from '@sentry/node';
import 'express-async-errors';

import sentryConfig from './config/sentry';

...

Sentry.init(sentryConfig);

...

this.exceptionHandler();

...

this.server.use(Sentry.Handlers.requestHandler());

...

this.server.use(Sentry.Handlers.errorHandler());

...

exceptionHandler() {
    this.server.use(async (err, req, res, next) => {
      if (process.env.NODE_ENV === 'development') {
        const errors = await new Youch(err, req).toJSON();

        return res.status(500).json(errors);
      }

      return res
        .status(500)
        .json({ error: 'Oops! Occoreu um erro no servidor!' });
    });
  }

Banco de dados (Docker com Postgres)

Após instalar o Docker, execute o comando:

docker run --name nome-do-container -e POSTGRES_PASSWORD=senha-acesso -p 5432:5432 -d postgres

Você pode usar o Postbird para criar o banco via interface (Gui)

Sequelize (ORM)

Execute os comandos

yarn add sequelize
yarn add sequelize-cli -D

Crie o arquivo .sequelizerc

const { resolve } = require('path');

module.exports = {
  config: resolve(__dirname,'src', 'config', 'database.js'),
  'models-path': resolve(__dirname,'src', 'app', 'models'),
  'migrations-path': resolve(__dirname,'src', 'database', 'migrations'),
  'seeders-path': resolve(__dirname,'src', 'database', 'seeds'),
}

Se estiver utilizando o Postgres, excute também

yarn add pg pg-hstore

Adcione ao arquivo .env

# Database

DB_HOST=localhost
DB_USER=postgres
DB_PASS=senha-acesso
DB_NAME=nome-do-seu-banco

Crie o arquivo src/config/database.js

require('dotenv/config');

module.exports = {
  dialect: 'postgres',
  host: process.env.DB_HOST,
  username: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_NAME,
  define: {
    timestamps: true,
    underscored: true,
    underscoredAll: true,
  },
};

Crie o arquivo src/database/index.js

import Sequelize from 'sequelize';

import databaseConfig from '../config/database';

const models = [];

class Database {
  constructor() {
    // conexão com o banco de dados
    this.connection = new Sequelize(databaseConfig);

    this.init();

    this.associate();
  }

  init() {
    models.forEach(model => model.init(this.connection));
  }

  associate() {
    models.forEach(
      model => model.associate && model.associate(this.connection.models)
    );
  }
}

export default new Database();

Migrations (Exemplo)

Crie a pasta src/database/migrations

Execute o comando

yarn sequelize migration:create --name=create-users

Exemplo do arquivo migration-create-users

Para criar a tabela users no banco de dados use o comando

yarn sequelize db:migrate

Para desfazer use o comando

yarn sequelize db:migrate:undo

Criptografia

Para criptografia iremos utilizar o bcryptjs

yarn add bcryptjs

Validação dados de entrada

Para as validações iremos utilizar o Yup

yarn add yup

Criação do Model e do Controller

Crie o model src/app/models/User.js

Crie o Controller src/app/controllers/UserController.js

Inclua as rotas no arquivo src/routes.js

routes.post('/users', UserController.store);

routes.put('/users', UserController.update);

Inclua no arquivo src/database/index.js

import User from '../app/models/User';

...

const models = [User];

JWT Token

Execute o comando

yarn add jsonwebtoken

Adcione ao arquivo .env

# Auth

APP_SECRET=9f14070c64a04b5144ff59f42d4edf7b

Chave gerada no site https://www.md5online.org/

Crie o arquivo src/config/auth.js

export default {
  secret: process.env.APP_SECRET,
  expiresIn: '30d',
};

Crie um middleware (src/src/app/middlewares/auth.js)

import jwt from 'jsonwebtoken';
import { promisify } from 'util';

import authConfig from '../../config/auth';

export default async (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    return res.status(401).json({ error: 'Token não informado!' });
  }

  const [, token] = authHeader.split(' ');

  try {
    const decoded = await promisify(jwt.verify)(token, authConfig.secret);

    req.userId = decoded.id;

    return next();
  } catch (err) {
    return res.status(401).json({ error: 'Token inválido!' });
  }
};

Crie um Controller src/app/controllers/SessionController.js

import jwt from 'jsonwebtoken';
import * as Yup from 'yup';

import User from '../models/User';
import authConfig from '../../config/auth';

class SessionController {
  async store(req, res) {
    const schema = Yup.object().shape({
      email: Yup.string()
        .email()
        .required(),
      password: Yup.string().required(),
    });

    if (!(await schema.isValid(req.body))) {
      return res.status(400).json({ error: 'Validação falou!' });
    }

    const { email, password } = req.body;

    const user = await User.findOne({ where: { email } });

    if (!user) {
      return res.status(401).json({ error: 'E-mail não encontrado!' });
    }

    if (!(await user.checkPassword(password))) {
      return res.status(401).json({ error: 'Senha inválida' });
    }

    const { id, name } = user;

    return res.json({
      user: {
        id,
        name,
        email,
      },
      token: jwt.sign(
        {
          id,
        },
        authConfig.secret,
        {
          expiresIn: authConfig.expiresIn,
        }
      ),
    });
  }
}

export default new SessionController();

Inclua no arquivo src/routes.js

import SessionController from './app/controllers/SessionController';
import authMiddleware from './app/middlewares/auth';

...

routes.post('/sessions', SessionController.store);
routes.use(authMiddleware);

Envio de E-mail

Veja aqui