Neste artigo, vamos aprender a criar um CRUD básico com NestJS, GraphQL e MySQL.
Entretanto, antes de qualquer coisa, vamos entender o conceito de todos esses frameworks? Então, vamos lá!
Segundo a documentação, NestJS é:
Nest (NestJS) é mais um framework, voltado principalmente para construção de aplicações Node.js para
servidores Back-end. Dessa forma, uma das vantagens de se usar o NestJS é a alta escalabilidade do lado do servidor e a estrutura automática criada incialmente, seguindo os princípios do S.O.L.I.D.
[adrotate banner=”4″]
Como o NestJS utiliza TypeScript, é possível a criação de códigos fortemente tipados, mas também é possível utilizá-lo com JS puro.
A construção do código inicialmente combina elementos de OOP (Programação Orientada a Objetos), FP (Programação Funcional) e FRP (Programação Reativa Funcional). Além disso, o Nest faz uso de estruturas robustas de servidor HTTP como o Express (o padrão). Bem como, opcionalmente, pode ser configurado para usar o Fastify também!
O Nest fornece um nível de abstração acima dessas estruturas comuns do Node.js (Express/Fastify), mas
também expõe suas APIs diretamente ao desenvolvedor. Dessa forma, oferecendo aos desenvolvedores a liberdade de usar a infinidade de módulos de terceiros que estão disponíveis para a plataforma subjacente.
Segundo a documentação, GraphQL é:
GraphQL é uma linguagem de consulta para APIs de alto desempenho, relacionado ao tempo de consulta aos principais SGBDs. Dessa forma, o GraphQL fornece uma descrição completa e compreensível dos dados e requisições de sua API, oferecendo aos clientes o poder de solicitar exatamente o que eles precisam e nada mais!
Primeiramente, para iniciar nosso projeto CRUD, iremos realizar todas as instalações necessárias para a criação do projeto.
Extensões:
Para visualizar o projeto deste artigo, clique neste link.
npm i -g @nestjs/cli
nest new nest-api-withgraphql
Então, após executar o comando acima, a imagem abaixo descreve o que vocês devem visualizar de saída.
3. Na imagem 1, escolha o seu gerenciador de pacotes favorito! Neste caso, vamos escolher o YARN.
Dessa forma, após as instalações para o NestJS estiverem finalizadas, a imagem abaixo descreve o que você deve visualizar de saída.
cd .NOME_DO_PROJETO
para acessar a pasta criada com o projeto que definimos. Logo após entrarmos no projeto, vamos abri-lo no VSCode com o comando code .
. Perfeito, agora podemos fechar o terminal!Documentações de referência para projeto CRUD: https://docs.nestjs.com/techniques/database e https://typeorm.io/.
CTRL + J
yarn add @nestjs/typeorm typeorm mysql2
Observação: vou fazer a instalação do mysql, porque este é o SGBD que mais utilizei na minha trajetória como programador, mas você pode utilizar outros, como postgresql, mssql, etc. É só fazer a instalação da lib e adicionar os parâmetros de conexão conforme apresentado no código abaixo.
Dentro do diretório src
, vamos modificar o arquivo app.module.ts
.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm'
/*
Arquivo para importação dos módulos utilizados no projeto e conexão com o banco de dados!
Sejam bem-vindos ao coração da nossa API!
*/
@Module({
imports: [
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "",
database: "graphql_database",
entities: [
"dist/**/*.entity{.ts,.js}"
],
synchronize: true
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
Caso queira saber mais sobre esses parâmetros, consulte a documentação neste link.
4. Ative os serviços de mysql e apache no XAMMP ou outro software da sua preferência!
Documentações de referência para projeto CRUD: https://docs.nestjs.com/graphql/quick-start, https://www.npmjs.com/package/graphql, https://www.npmjs.com/package/@nestjs/apollo, https://www.npmjs.com/package/apollo-server-express e https://www.npmjs.com/package/@nestjs/graphql.
yarn add @nestjs/graphql @nestjs/apollo graphql graphql-tools apollo-server-express
src
, vamos modificar o arquivo app.module.ts
novamente, com a nova funcionalidade do GraphQLModule:import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm'
//importações para utilização do graphql
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';
@Module({
imports: [
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "",
database: "graphql_database",
entities: [
"dist/**/*.entity{.ts,.js}"
],
synchronize: true
}),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
Adicione GraphQLModule dentro do array de imports: []. Conforme o código supracitado! Assim, vamos utilizar o autoSchemaFile para fazer a geração do arquivo de schema.gql com as tipagens de forma automática e utilizar o servidor do Apollo para utilização do GraphQL.
nest g module user
Dessa forma, a imagem abaixo descreve o que você deve visualizar de saída.
O comando irá instanciar automaticamente o módulo de usuário e criar o arquivo. Assim, o mesmo acontecerá para os próximos passos.
nest g s user
nest g r user
src/user
com o seguinte nome user.entity.ts
Documentações de referência para projeto CRUD: https://docs.nestjs.com/graphql/resolvers e https://docs.nestjs.com/graphql/cli-plugin.
As entidades se assemelham bastante com a estrutura de migrations. Então, é nesse arquivo que definimos como vai ficar estruturado as colunas da tabela em nosso banco de dados!
(src/user/user.entity.ts)
, o seguinte trecho de código:// importações
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@ObjectType() //Informa que é um objeto
@Entity() //Especifica ao código que essa classe se trata de uma entidade
export class User {
@PrimaryGeneratedColumn() // Especifica ao NestJS que a coluna id é uma coluna de chave primaria que é gerada automaticamente
@Field(() => ID) // Informa ao graphql o tipo da coluna
id: string; // tipagem da coluna
@Column()
name: string;
@Column()
email: string;
}
nest-cli.json
na raiz do nosso projeto.{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/graphql/plugin",
"options": {
"typeFileNameSuffix": [
".input.ts",
".args.ts",
".entity.ts",
".type.ts"
]
}
}
]
}
}
Documentação de referência para projeto CRUD: https://docs.nestjs.com/techniques/validation.
yarn add class-validator
yarn add class-transformer
src/user
, crie uma pasta e nomeie como dto
e adicione dois arquivos dentro desta pasta chamada createuser.input.ts
e update-user.input.ts
create-user.input.ts
, adicione o seguinte trecho de código:// importações
import { InputType } from "@nestjs/graphql";
import { IsEmail, IsNotEmpty, IsString } from "class-validator";
@InputType() //Define que essa classe é do tipo input, que são os valores que serão recebidos pelo endpoint
export class CreateUserInput {
@IsString()// Validação do tipo do input
@IsNotEmpty({ message: "Campo de name obrigatório" })//Validação para não aceitar o valor vazio para chave
name: string; //Tipagem da coluna
@IsEmail()// Validação do tipo do input
@IsNotEmpty({ message: "Campo de email obrigatório" })//Validação para não aceitar o valor vazio para chave
email: string; //Tipagem da coluna
}
Então, dentro do arquivo update-user.input.ts
, adicione o seguinte trecho de código:
// importações
import { InputType } from "@nestjs/graphql";
import { IsEmail, IsNotEmpty, IsOptional, IsString } from "class-validator";
@InputType() //Define que essa classe é do tipo input, que são os valores que serão recebidos pelo endpoint
export class UpdateUserInput {
@IsString()// Validação do tipo do input
@IsNotEmpty({ message: "Campo de name obrigatório" })//Validação para não aceitar o valor vazio para chave
@IsOptional()// Define o campo como opcional
name?: string; //Tipagem da coluna
@IsEmail()// Validação do tipo do input
@IsNotEmpty({ message: "Campo de email obrigatório" })//Validação para não aceitar o valor vazio para chave
@IsOptional()// Define o campo como opcional
email?: string; //Tipagem da coluna
}
6. Adicione, no arquivo src/main.ts
, o uso global das validações:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()) //Para utilizar as validações globalmente
await app.listen(3000);
}
bootstrap();
Documentação de referência para projeto CRUD: https://docs.nestjs.com/providers#services
Service é o arquivo responsável por conter todas nossas funções que faram comunicação direta com o banco. Além disso, ele se assemelha muito aos controllers em outros frameworks.
Portanto, vamos modificar o arquivo src/user/user.service.ts
com o seguinte trecho de código:
// importações
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserInput } from './dto/create-user.input';
import { UpdateUserInput } from './dto/update-user.input';
import { User } from './user.entity'
@Injectable() // para fazer injeção de dependências no nestjs
export class UserService {
//injeta o repositório utilizando a entidade de usuário
constructor(
@InjectRepository(User)
private userRepository: Repository<User>
) { }
//método para listagem de usuários
async findAllUsers(): Promise<User[]> {
const users = await this.userRepository.find()
return users
}
//método para trazer um usuário por id
async findUserById(id: string): Promise<User> {
const user = await this.userRepository.findOneBy({ id })
if (!user) {
throw new NotFoundException("Usuário não encontrado")
}
return user
}
//método para criar um usuário
async createUser(data: CreateUserInput): Promise<User> {
const user = this.userRepository.create(data);
const userSaved = await this.userRepository.save(user)
if (!userSaved) {
throw new InternalServerErrorException('Falha na criação do usuário')
}
return userSaved
}
//método para alterar um usuário
async updateUser(id: string, data: UpdateUserInput): Promise<User> {
const user = await this.findUserById(id);
await this.userRepository.update(user, { ...data });
const userUpdated = this.userRepository.create({ ...user, ...data })
return userUpdated;
}
//método para exclusão de um usuário
async deleteUser(id: string): Promise<boolean> {
const user = await this.findUserById(id);
const deleted = await this.userRepository.delete(user);
if (deleted) {
return true;
}
return false;
}
}
Documentação de referência para projeto CRUD: https://docs.nestjs.com/graphql/resolvers#resolvers
Os Resolvers fornecem instruções para transformar uma operação do GraphQL (@Query @Mutation) em dados. Assim, aqui ficará definido o que uma função recebe e o que ela retorna!
src/user/user.resolver.ts
com o seguinte trecho de código:// importações
import { Args, Mutation, Resolver, Query } from '@nestjs/graphql';
import { CreateUserInput } from './dto/create-user.input';
import { UpdateUserInput } from './dto/update-user.input';
import { User } from './user.entity';
import { UserService } from './user.service';
@Resolver('User')
export class UserResolver {
//Ejetar nosso userService
constructor(private userService: UserService) { }
@Query(() => [User]) //informa ao graphql que esse método vai retornar um array de usuários e que não há modificação do banco com ela
async users(): Promise<User[]> {
const users = await this.userService.findAllUsers()
return users
}
@Query(() => User) //informa ao graphql que esse método vai retornar um usuário em especifico e que não há modificação do banco
async user(@Args('id') id: string): Promise<User> {
const user = await this.userService.findUserById(id)
return user
}
@Mutation(() => User) //informa ao graphql que esse método vai modificar o estado no nosso banco de dados
async createUser(@Args('data') data: CreateUserInput): Promise<User> {
const user = await this.userService.createUser(data)
return user
}
@Mutation(() => User) //informa ao graphql que esse método vai modificar o estado no nosso banco de dados
async updateUser(
@Args('id') id: string,
@Args('data') data: UpdateUserInput
): Promise<User> {
const user = this.userService.updateUser(id, data);
return user;
}
@Mutation(() => Boolean) //informa ao graphql que esse método vai modificar o estado no nosso banco de dados
async deleteUser(
@Args('id') id: string
): Promise<boolean> {
const deleted = await this.userService.deleteUser(id);
return deleted;
}
}
2. Vamos modificar o arquivo src/user/user.module.ts
com o seguinte trecho de código:
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({
imports: [
TypeOrmModule.forFeature([User])
],
providers: [UserService, UserResolver]
})
export class UserModule { }
yarn run start:dev
Com isso, a saída deve se apresentar como na imagem abaixo:
http://localhost:3000/graphql
Dessa forma, após colar o endereço supracitado no navegador, a seguinte página deve aparecer:
Esse é o playground do graphql. Ou seja, a ferramenta que auxilia em nossas requisições!
query{
users{
id
name
email
}
}
Assim, ao fazer a consulta na aba esquerda, a aba direita da ferramenta deve exibir o seguinte resultado:
Isso acontece porque não temos nenhum usuário registrado.
mutation {
createUser(
data: {
name: "Edith Silva"
email: "[email protected]"
}
) {
id
name
email
}
}
Dessa forma, a resposta deve aparecer como na imagem abaixo:
Então, à essa altura, já conseguimos listar o usuário criado!
// Em uma nova aba do playground, adicione o body abaixo para fazer uma busca pelo id
query{
user(id: "1"){
id
name
email
}
}
// Em uma nova aba do playground, adicione o body abaixo para fazer uma Alteração
mutation {
updateUser(id: "1", data: { email: "[email protected]" }) {
id
name
email
}
}
//Em uma nova aba do playground, adicione o body abaixo para fazer uma exclusão
mutation {
deleteUser(id: "1")
}
Veja também:
Entenda gerenciador de pacotes
Assim encerramos nossa implementação de um CRUD básico de usuários, utilizando o NestJS, GraphQL e SGBD MySQL. O que foi ensinado nesse artigo é uma pequena fração de tudo que o CRUD essas demais tecnologias podem nos entregar!
Dessa forma, sugiro que a partir daqui, vocês explorem cada vez mais a própria documentação e qualquer dúvida, estou aberto para discussões através do Discord (Manoel Fernandes Neto#5644) ou Linkedin.
Autor: Manoel Fernandes Neto.
[adrotate banner=”5″]
Autor