Criando uma aplicação web com Dart. Parte 1: Aqueduct: Básico, conexão com banco e autenticação JWT
O Dart é uma linguagem de programação criada dentro da Google em 2011 com o objetivo de substituir o javascript como linguagem principal nos navegadores. porém, devido a sua facilidade e curva de aprendizado, acabou se tornando uma linguagem com propósito geral, podendo hoje ser utilizada no backend, frontend e principalmente no mobile e desktop utilizando o framework Flutter.
Nesse tutorial, iremos utilizar o dart para construir o básico de uma aplicação “to-do list” utilizando o framework Aqueduct.
Requisitos
É necessário estar instalado na maquina o SDK do dart, pela linguagem ser multi plataforma, sua instalação pode ser feita tanto para Windows, Linux ou macOS. neste tutorial, estaremos utilizando a versão 2.5.
Com o Dart instalado, abra um terminal e digite o seguinte comando para realizar a instalação do aqueduct pelo pub, que é o próprio gerenciador de pacotes do Dart.
pub global activate aqueduct
Estaremos utilizando o Visual Studio Code para editar nossos códigos junto com a extensão oficial do Dart. E para testar a aplicação, estaremos utilizando o PostMan.
Inicio do projeto
Com as ferramentas necessárias já instaladas, abra um novo terminal em sua pasta de projetos e rode o seguinte comando para realizar a criação do projeto.
aqueduct create todo
Para abrir o projeto no visual studio code, navegue para dentro da pasta utilizando o comando
cd todo
e em seguida
code .
Dentro da pasta lib, existe um arquivo chamado channel.dart, esse arquivo é onde será declarado as nossa rotas e controllers.
O primeiro passo que iremos fazer será criar uma pasta dentro da pasta lib chamada models, onde será criado nossas classes modelos. O primeiro arquivo que criaremos será a classe de ToDo, no caso to_do.dart.
Dentro desse arquivo, criaremos uma classe estendendo de uma outra classe chamada Serializable, isso é necessário para serializar o ToDo para JSON e vice versa.
Além das propriedades da classe, sendo elas id, name e done, também teremos dois métodos que fazem parte da classe herdada Serializable, sendo elas o asMap e readFromMap. No final, a classe ficará da seguinte maneira:
Em seguida, criaremos uma pasta dentro da pasta lib chamada controllers, onde iremos criar um arquivo chamado to_do_controller.dart.
Dentro desse arquivo, criaremos uma classe que irá ser estendida de ResourceController, que é a classe padrão do aqueduct para a criação de métodos http dentro de um controller.
A controller com os principais métodos http e uma lista de ToDo’s mockados para fins de testes, ficaria assim:
Por fim, precisaremos incluir a controller de “todos” e qual será a sua rota no arquivo channel.dart.
Em seguida, abra o terminal do visual studio code utilizando o comando Ctrl + shift + ` e rode o comando “aqueduct serve” para rodar a aplicação.
Com o postman, você já poderá fazer requisições para a api:
GET: localhost:8888/todo
GET: localhost:8888/todo/1
POST: localhost:8888/todo
PUT: localhost:8888/todo
DELETE: localhost:8888/todo/1
Porém, você perceberá que os dados não são persistidos, por exemplo, ao fazer um método post ou delete, isto acontece por que o Aqueduct reseta o estado da aplicação para cada request, isso é melhor explicado no seguinte link da documentação oficial:
Alguns links interessantes:
Conexão com banco de dados PostgreSQL
Agora, iremos fazer algumas modificações para fazer acesso a um banco de dados Postgres em nossa API.
Primeiramente, é necessário uma instalação do Postgres na maquina de desenvolvimento, podendo ela ser feita direto na maquina ou utilizando o Docker.
Estarei utilizando neste tutorial, a imagem oficial do Postgres no dockerhub, para subir o serviço na minha maquina, utilizarei o seguinte comando no terminal:
docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres:9.6
Com o projeto aberto no Visual Studio Code, faremos algumas modificações na model de todo para ser feito o uso dela no banco de dados, a classe não precisará estender de Serializable pois a classe ManagedObject já estende de Serializable.
Em seguida, faremos algumas modificações no arquivo channel.dart
Aqui criamos uma variável context que guardará as informações de banco de dados e em seguida, iremos passar ela para o nosso controller de ToDo fazer as consultas de banco de dados. Precisaremos preparar o nosso controller para receber o contexto e fazer as operações de banco de dados, ficando assim:
Ajustamos o controller para receber o contexto de banco de dados e modicamos todos os métodos http para fazer consultas ao banco de dados utilizando a classe Query do Aqueduct. Mais informações sobre o ORM podem ser encontradas nos links:
Nosso projeto já está quase pronto para funcionar com banco de dados, agora falta somente fazer a migração da model ToDo para o banco de dados, o primeiro passo é criar um arquivo na pasta raiz do projeto chamada database.yaml, que será utilizado pelo aqueduct para localizar o banco de dados e fazer as migrações, o conteúdo do arquivo será então:
Por fim, iremos criar a migração. Com o terminal do Visual Studio Code aberto, rode o seguinte comando para criar uma migração:
aqueduct db generate
Com esse comando, o Aqueduct detecta as models do projeto para criar a migração, as migrações ficam dentro do diretório migrations.
Para aplicar a migração, precisaremos rodar o comando:
aqueduct db upgrade
Aplicada a migração, você já poderá testar o projeto realizando requisições HTTP, você perceberá que os dados agora estão sendo persistidos.
Autenticação JWT
Como qualquer aplicação web, é necessário algum tipo de autenticação. Uma das maneiras mais comuns que se tem hoje em dia para realizar autenticação em API’s REST é utilizando um padrão chamado JWT, para mais informações, recomendo a leitura do seguinte artigo do Wellington Nascimento sobre o assunto:
Neste artigo, demonstrarei como aplicar esse padrão de autenticação na nossa API em Dart.
Relacionamento one-to-many
A primeira coisa que iremos fazer será criar um model para o usuário e fazer a relação com o ToDo. Dentro da pasta models, criaremos então uma classe user.dart:
Perceba que existe uma propriedade ManagedSet que será responsável pela relacionamento one-to-many com o ToDo, mas precisaremos incluir o relacionamento também na classe de ToDo, portanto, adicione o atributo a seguir na classe todo.dart:
@Relate(#toDo)User user;
Por fim, precisaremos criar uma migration no banco de dados, para criar uma migration, utilizamos o comando
aqueduct db generate
Na migration criada, iremos fazer uma pequena alteração, pois ela incluiu a criação de uma coluna password na tabela _user, o que não será necessário pois gravaremos somente o hash da senha no banco de dados, então sinta-se a vontade para remover o seguinte trecho da migration:
SchemaColumn("password", ManagedPropertyType.string,isPrimaryKey: false,autoincrement: false,isIndexed: false,isNullable: false,isUnique: false),
Para aplicar a migration, utilize o comando:
aqueduct db upgrade
Geração do token JWT
Em seguida, importaremos uma dependência que será utilizada para gerar e descriptografar o token JWT. Então no arquivo pubspec.yaml, inclua a dependência:
jaguar_jwt: ^2.1.6
Em seguida, criaremos um diretório dentro de lib chamado utils e dentro desta pasta, criar um arquivo utils.dart que será utilizado para deixar funções comuns que serão utilizadas por todo o projeto. Dentro deste arquivo, teremos alguns atributos e métodos:
- jwtKey — A chave privada para a criação dos tokens JWT.
- generateJWT — Método que receberá um usuário e retornará uma string (o próprio token JWT).
- generateSHA256Hash — Método que irá gerar o hash da senha do usuário.
O arquivo utils.dart ficará assim:
Agora, precisaremos criar dois novos controllers, o primeiro controller será utilizado para criar um novo usuário e gerar um hash da senha para o mesmo, ficando assim:
E por fim, um controller que será utilizado para validar o usuário e devolver um token autenticado para ele, chamaremos ele de session_controller.dart.
Middleware de autenticação
No Aqueduct, todo controller é um middleware, você pode inclusive encadear controllers para se chegar em um resultado esperado, é isso que iremos fazer, antes da requisição chegar no ToDoController, precisaremos ter um middleware que verificará se o token JWT passado pelo header é realmente válido. Para fazer isso, criaremos uma pasta dentro de lib chamada middlewares, e dentro dela, uma classe jwt_middleware.dart que terá o comportamento muito parecido com um controller, porém não irá retornar nada para o usuário, somente passar a requisição adiante para o próximo controller.
Neste middleware, vamos descriptografar o token, pegar o id do usuário no token e fazer algumas validações (ver se o usuário existe, verificar a data de expiração do token, etc) e por fim, passar o usuário do token para o próximo controller.
Para mais informações sobre como validar os dados do token:
Em seguida, precisaremos fazer algumas modificações na variável routes do arquivo channel.dart para incluir os novos controllers e o novo middleware de autenticação.
router.route("/todo/[:id]").link(() => JwtMiddleware(context)).link(() => ToDoController(context));router.route("/user/[:id]").link(() => UserController(context));router.route("/session/[:id]").link(() => SessionController(context));
Por fim, precisaremos fazer algumas modificações no ToDoController, pois agora o mesmo precisa filtrar os todo’s também pelo id do usuário, existem diversas maneiras de se fazer isso, mas para deixar simplificado, utilizaremos o id que foi obtido do token.
Testes
Agora você poderá criar, editar, listar e deletar os ToDo’s baseado no usuário logado.
Com isso, terminamos o nosso backend em Dart, para finalizar, no próximo artigo demonstrarei como criar um frontend para consumir a API criada utilizando o framework AngularDart.
O repositório do projeto pode ser encontrado no seguinte link do meu GitHub: