Backend - nodebr/nodebr GitHub Wiki

Variáveis de ambiente

Algumas variáveis de ambiente são fundamentais para que a aplicação e/ou os testes rodem, você pode verificar o arquivo .env para saber quais as variáveis você irá necessitar.

Este arquivo é só um template e não será carregado automaticamente pela aplicação. Você também pode modificar estas variáveis para que a aplicação funcione corretamente, exemplo para usuários que utilizam o bash como terminal: NODE_ENV=production npm run start.

Exemplos

Você pode iniciar seus estudos com a funcionalidade hello-world, esta funcionalidade somente é carregada quando a aplicação é iniciada em modo de desenvolvimento e contém todas as características que você irá necessitar para desenvolver outras funcionalidades da aplicação.

Iniciando a aplicação

Para iniciar e rodar a aplicação localmente você precisará seguir alguns passos para garantir que tudo irá rodar como planejado:

  • Instale todos os pacotes necessários com o comando npm install
  • Verifique se todas as variáveis de ambiente estão corretas, principalmente a DATABASE_URL
  • Verifique se o banco de dados está rodando e o usuário e senha estão de acordo com a variável de ambiente DATABASE_URL
  • Verifique se você rodou a migração com o comando npm run knex -- migrate:latest
  • Inicie a aplicação com o comando npm start

Estrutura do projeto

O projeto é estruturado de forma simples e separado por funcionalidades:

  • lib esta pasta contém arquivos que podem ser compartilhados entre as demais partes do projeto. Ex: a biblioteca db é utilizada tanto dentro da pasta resources quanto da pasta test.
  • resources cada funcionalidade da aplicação deve ficar dentro da sua respectiva pasta. Assim uma funcionalidade pode ter os seguintes arquivos:
    • model.js contém definição de modelo para esta funcionalidade se a mesma necessitar de um modelo
    • routes.js contém as definições de rotas caso a funcionalidade necessite declarar algum endpoint de acesso
    • schemas.js contém um grupo de schemas de validação que podem ser utilizados pelas rotas ou modelos
    • handlers.js contém todos os controladores que o arquivo routes.js irá utilizar quando definir as rotas

Roteamento

Toda funcionalidade que apresentar um arquivo routes.js terá sua rota incluída na aplicação automaticamente, sendo assim você não precisa se preocupar em inserir nenhuma rota na aplicação mas somente criar o arquivo e declarar suas rotas.

Imagine que você necessitasse criar uma rota GET /hello-world. para isso seria necessário apenas criar o arquivo resources/hello-world/routes.js com o seguinte conteúdo:

const router = require('express').Router()
const handlers = require('./handlers')

// Declare a rota que você necessita
router.get('/hello-world', handlers.findAll)

// Não esqueça de exportar o seu router para que ele seja incluído automaticamente na aplicação
module.exports = router

Handlers

Os handlers são os arquivos mais fáceis de entender pois representam um grupo de controladores para uma determinada funcionalidade. Se quisermos implementar o handler findAll para o exemplo acima basta criar o arquivo resources/hello-world/handlers.js com o seguinte conteúdo:

exports.findAll = (req, res) => {
  // Deste ponto em diante é simplemente Express
  res.status(422).send({ error: true })
}

Validação de dados

Toda rota que irá receber dados externos necessita de validação. Utilizando o mesmo exemplo acima podemos imaginar a necessidade de uma rota que receba nome e sobrenome, para isso basta declarar um schema de validação utilizando o módulo Joi dentro do arquivo resources/hello-world/schemas.js. Nosso arquivo ficaria assim:

const Joi = require('joi')

exports.create = Joi.object({
  nome: Joi.string().required(),
  sobrenome: Joi.string.required()
})

Com a definição do schema pronta, basta criar uma rota para utilizar este schema. Para utilizar o Joi como middleware para o Express utilizamos uma pequena ferramenta dentro de nossas lib: lib/validator.js. Com esta lib fica muito fácil de utilizar nosso schema com nossa rota:

const bodyParser = require('body-parser')

const router = require('express').Router()
const handlers = require('./handlers')
const schemas = require('./schemas')
const validator = require('../../lib/validator')

router.post('/hello-world', 
  bodyParser.json(), // Não esqueça de utilizar o bodyParser antes da validação
  validator({ body: schemas.create }), // Você pode verificar qualquer aspecto da requisição: body, headers, query, params...
  handlers.findAll)

module.exports = router

Com isso pronto você não precisará se preocupar em validar os dados no seu handler pois caso o mesmo seja executado significa que o campo req.body contém dados validados.

Outro aspecto positivo de utilizar esta lib para validação é que a mesma cuida da resposta automaticamente em caso de dados inválidos, deixando seu handler mais enxuto.

Aconselhamos que você leia a documentação do Joi para aprender mais como utilizar esta biblioteca.

Sessões

As sessões dependem da biblioteca lib/session.js, basta incorporar ela em sua rota para proteger um handler. Abaixo está um exemplo de uma rota que requer autenticação.

...
const session = require('../../lib/session')

router.get('/protegido', session(), handlers.protegido)

Agora o handler protegido terá acesso ao ID do usuário que está logado a partir do object req.session.user_id. Você não precisa se preocupar em verificar se o usuário tem uma sessão ou não pois a lib session automaticamente interrompe a sessão caso alguém esteja tentando acessar um endpoint protegido sem estar logado.

Migrações

Toda e qualquer alteração no banco de dados necessita de uma migração. Imagine um cenário onde seria necessário adicionar a coluna email na tabela usuarios. O primeiro passo para criar esta migração será rodar o comando npm run knex -- migrate:make adicionar_coluna_email. Como você pode perceber o último parâmetro é o nome da sua migração. Este comando irá gerar um arquivo e mostrar no seu terminal o caminho para o arquivo gerado.

> [email protected] knex /Users/alanhoff/Projects/nodebr
> knex --knexfile ./lib/db/knexfile.js "migrate:make" "adicionar_coluna_email"

Working directory changed to ~/Projects/nodebr/lib/db
Using environment: development
Created Migration: ./lib/db/migrations/20161115012233_adicionar_coluna_email.js

Com este arquivo criado basta modifica-lo para refletir as mudanças que desejamos no nosso banco de dados. Neste exemplo o arquivo ficaria assim:

// A função up serve como instrução mara migrar o banco de dados
exports.up = knex => knex.schemas.table('usuarios', table => {
  table.string('email').notNull()
})

// Já a função down serve para fazer o rollback caso seja necessário
exports.down = knex => knex.schemas.table('usuarios', table => {
  table.dropColumn('email')
})

Com este arquivo pronto basta rodar o comando npm run knex -- migrate:latest para que o seu banco de dados seja atualizado com as modificações que você adicionou.

Atenção: Uma vez que um arquivo de migração rodar, o knex não rodará o arquivo novamente mesmo que você o altere. Para resolver isto você pode realizar um rollback com o comando npm run knex -- migrate:rollback e logo após realizar a migração novamente ou apagar o seu banco de dados completamente, recriá-lo e rodar a migração novamente.

Recomendamos que você leia a documentação do knex para saber como utilizá-lo.

Modelos

Toda a interação com o banco de dados na lógica da aplicação é realizada através de modelos. Para criar um modelo basta criar um arquivo model.js na pasta da funcionalidade específica. Podemos imaginar que queremos criar o modelo para a tabela usuarios, para isto seria necessário criar o arquivo resources/usuarios/model.js com o seguinte conteúdo:

module.exports = bookshelf => bookshelf.model('Usuario', {
  tableName: 'usuarios'
})

Após isso nossa biblioteca lib/db se encarregará de carregar o seu modelo automaticamente quando necessário. Para utilizar o seu modelo recém criado basta realizar o require da lib/db em qualquer parte do projeto:

const db = require('../../lib/db')
const Usuario = db.model('Usuario') // Assim carregamos o modelo que desejamos utilizar

Usuario.forge({
  nome: 'Alan',
  sobrenome: 'Hoffmeister',
  email: '[email protected]'
})
.save()
.then(() => console.log('Usuário criado!'))

Alguns módulos estão ativados por padrão, incluíndo o módulo bookshelf-uuid. Este módulo se encarrega de criar uma GUID para cada nova informação salva no banco de dados, por isso é necessário cuidar para que quando criar uma migração que cria uma tabela você tenha certeza que também estará incluíndo os seguintes campos:

exports.up knex => knex.schema.createTableIfNotExists('usuarios', table => {
  // Os campos abaixo são obrigatórios
  table.uuid('id').primary()
  table.timestamps()

  // Qualquer outro campo é opcional de acordo com a funcionalidade que você está criando
  table.string('nome').notNullable()
  table.string('sobrenome').notNullable()
  table.string('email').notNullable()
})

Aconselhamos que você leia a documentação do bookshelf para familiarizar-se com a ferramenta.