GraphQL é uma forma poderosa e eficiente de construir APIs. O cliente consulta a API como consultaria um banco de dados, e a API retorna apenas os dados solicitados, geralmente reduzindo o payload e melhorando o tempo de resposta. E tempos de resposta baixos são críticos no mundo atual.
Quando a API GraphQL é combinada com MongoDB, além da rapidez, você ganha um formato de dados consistente de ponta a ponta.
Imagine o seguinte: seu cliente executa uma consulta GraphQL que se parece com JSON. Quando esses dados chegam à sua aplicação — digamos que seja em TypeScript — você continua trabalhando com um formato semelhante a JSON na aplicação. Indo além, ao usar MongoDB, os dados enviados e recebidos do banco também são parecidos com JSON. Ou seja, você tem uma experiência de dados consistente com alto desempenho. Menos preocupação em manipular e formatar dados e mais foco na experiência do usuário da aplicação, sem se prender ao banco e às ferramentas.
Neste tutorial, você vai ver como é simples usar MongoDB na sua API GraphQL, desta vez construída com TypeScript.
Pré-requisitos
Para este tutorial, garanta que você tem o seguinte pronto para uso:
Um cluster do MongoDB Atlas
Node.js 22+
MongoDB Node.js Driver 6.x
Não faremos nada muito elaborado no banco neste tutorial, então qualquer tier do cluster MongoDB funciona, inclusive o gratuito. Contudo, não vamos cobrir o provisionamento de um cluster MongoDB Atlas aqui. A expectativa é que você já tenha regras de usuário e de rede configuradas. Se precisar de ajuda para começar, confira a documentação Get Started With Atlas sobre o tema.
Você vai precisar ter uma aplicação Node.js criada. Se preferir, execute os comandos abaixo:
mkdir graphql_example
cd graphql_example
npm init -yTecnologia
Os comandos acima criam um novo diretório de projeto e um package.json básico. Usaremos TypeScript, Express, MongoDB e Apollo neste projeto. Instale tudo com os comandos a seguir:
Se em algum momento você quiser ver o projeto completo ou testá-lo, confira no GitHub.
Crie a base com Apollo, Express e dependências do MongoDB
Em termos de estrutura, vamos usar os seguintes arquivos:
src/database/connection.ts
src/database/userService.ts
src/resolvers/index.ts
src/schema/typeDefs.ts
src/types/index.ts
src/index.ts
.env
O arquivo src/index.ts terá todo o bootstrap do Express e do Apollo. Ou seja, iniciar o servidor, definir os endpoints GraphQL etc. O arquivo src/types/index.ts conterá as definições TypeScript para trabalhar com o modelo de dados do banco dentro da aplicação. Não confunda com o src/schema/typeDefs.ts, que, apesar de parecido, foca nas definições de tipos do GraphQL.
Isso nos deixa com os diretórios database e resolvers. A maior parte da lógica da aplicação e do banco ficará neles, como veremos já já.
Por agora, vamos configurar o servidor no arquivo src/index.ts:
Para começar, importamos todas as dependências do projeto. Em seguida, configuramos o servidor Express e o middleware do Apollo.
const server =newApolloServer({
typeDefs,
resolvers,});Tecnologia
Ainda não adicionamos nada aos arquivos src/schema/typeDefs.ts e src/resolvers/index.ts, mas já deixamos tudo pronto. O servidor não vai subir sem erros até preenchermos essas partes.
Vale mencionar que este exemplo permite requisições de todas as origens por causa do seguinte:
Isso é bom para desenvolvimento, mas em produção limite às origens necessárias.
Interagindo com o MongoDB a partir da aplicação TypeScript com Node.js
Apesar de já termos o Express e o Apollo configurados, ainda não conectamos o MongoDB na aplicação. Antes de ir ao código, adicione as informações de conexão no arquivo .env:
Substitua <USERNAME>, <PASSWORD> e <HOST> pelas informações do seu cluster no dashboard do MongoDB Atlas.
Agora, vamos estabelecer a lógica de conexão do banco. No arquivo **src/database/connection.ts**, adicione o código abaixo:
import{ MongoClient, Db }from'mongodb';let db: Db |null=null;let client: MongoClient |null=null;exportasyncfunctionconnectToDatabase():Promise<Db>{if(db){return db;}try{constMONGODB_URI= process.env.MONGODB_URI;if(!MONGODB_URI){thrownewError('MONGODB_URI is not set');}constDB_NAME= process.env.DB_NAME||'graphql_example';constAPP_NAME= process.env.APP_NAME||'devrel-graphql';console.log('Connecting to MongoDB...');
client =newMongoClient(MONGODB_URI,{ appName:APP_NAME});await client.connect();
db = client.db(DB_NAME);console.log(Successfully connected to MongoDB database: ${DB_NAME});return db;}catch(error){console.error('MongoDB connection error:', error);throw error;}}exportfunctiongetDatabase(): Db {if(!db){thrownewError('Database not initialized. Call connectToDatabase() first.');}return db;}exportasyncfunctioncloseDatabase():Promise<void>{if(client){await client.close();
db =null;
client =null;console.log('MongoDB connection closed');}}Tecnologia
A ideia aqui é criar uma conexão singleton. Existem várias formas de usar o MongoDB na sua aplicação, mas escolhemos este caminho para este projeto.
A função connectToDatabase lê as informações do arquivo **.env**, conecta e retorna o banco definido. Se falhar, o erro é capturado e propagado. Você só deve precisar dessa função na configuração inicial do servidor, como vimos no arquivo anterior.
A função getDatabase será usada sempre que uma requisição chegar à aplicação. Ela retorna a instância atual da conexão.
Para manter tudo organizado, temos a função closeDatabase para encerrar a conexão quando o servidor terminar. É opcional, mas uma boa prática.
Antes da lógica de banco, vale definir nossos tipos TypeScript para os DTOs (data transfer objects):
As interfaces acima, no arquivo **src/types/index.ts**, são gerenciadas pela nossa API, mas a lógica de banco também precisa conhecê-las. Você verá já já.
Isso nos leva às interações reais de banco para criar, ler, atualizar e excluir (CRUD), feitas no arquivo src/database/userService.ts. A ideia é: os resolvers do GraphQL tratam a lógica da aplicação e chamam as funções deste serviço, e o serviço cuida da lógica do banco.
No arquivo src/database/userService.ts, temos o seguinte código:
Ela é parecida com a interface User, mas não idêntica. UserDocument modela mais estritamente como o banco trabalha. No banco usamos ObjectId, mas na aplicação usamos strings. No MongoDB, o id é _id, não id, como na interface User.
Isso nos leva à classe UserService.
Depois de obter o handle da coleção a partir da instância do banco no constructor, temos a seguinte série de funções:
toUser
getAllUsers
getUserById
createUser
updateUser
deleteUser
createIndexes
A função toUser é um helper para converter entre User e UserDocument. Enviamos e recebemos UserDocument do banco, e precisamos converter para User ao expor pela aplicação.
A função createIndexes cria alguns índices de exemplo que podem ser úteis. Por padrão, existe índice apenas em _id, o que basta para um demo pequeno, mas conforme sua aplicação cresce, não deixe de criar índices adequados aos seus dados e consultas. Os dois acima são apenas exemplos.
Agora podemos olhar cada função do serviço, começando por getAllUsers:
Essa função busca todos os documentos da coleção, pois não há filtro em find. Os resultados são convertidos com toUser e retornados para a aplicação, que serão usados pelos resolvers do Apollo.
A função getByUserId é parecida, mas agora usamos findOne com um critério de filtro — neste caso, buscar um único usuário pelo _id, que é único. Como o id em User é string, precisamos envolvê-lo em ObjectId para a consulta no MongoDB.
Agora, criar dados no MongoDB com createUser:
asynccreateUser(input: CreateUserInput):Promise<User>{const newUser: Omit<UserDocument,'_id'>={
name: input.name,
email: input.email,
age: input.age,
createdAt:newDate(),};const result =awaitthis.collection.insertOne(newUser as UserDocument);const createdUser =awaitthis.collection.findOne({ _id: result.insertedId });if(!createdUser){thrownewError('Failed to create user');}returnthis.toUser(createdUser);}Tecnologia
Após usar insertOne para inserir, usamos o insertedId retornado para consultar o registro. Fazemos isso porque, em uma API GraphQL, ao criar dados, o esperado é já poder consultá-los na mesma mutation.
Nesta aplicação, permitimos atualização apenas dos campos name, email e age. Todos são opcionais, então só os incluímos no critério de atualização se estiverem presentes.
Isso nos leva ao findOneAndUpdate.
O primeiro objeto é o filtro. Só queremos atualizar quando o _id corresponder. Para cada correspondência, usamos o operador $set para substituir ou criar os campos em updateFields. Campos ausentes não são alterados. Por fim, como estamos encontrando e atualizando, definimos returnDocument como after para receber o documento já alterado. Se usássemos updateOne, teríamos apenas informações da operação, não os dados.
A última operação de CRUD é remover dados do MongoDB com deleteUser:
asyncdeleteUser(id:string):Promise<boolean>{try{const result =awaitthis.collection.deleteOne({ _id:newObjectId(id)});return result.deletedCount >0;}catch(error){returnfalse;}}Tecnologia
A função deleteOne se assemelha à findOne pois usa o filtro por _id. A diferença é que qualquer correspondência — no máximo um documento — será removida.
Agora a lógica de banco está pronta. Para finalizar, conecte-a a partir de **src/index.ts**:
// Previous imports here...import{ connectToDatabase, closeDatabase }from'./database/connection';import{ UserService }from'./database/userService';asyncfunctionstartServer(){try{awaitconnectToDatabase();const userService =newUserService();await userService.createIndexes();// The rest of the file here...}Tecnologia
No trecho acima, omitimos a maior parte do src/index.ts. A ideia é só mostrar o uso de connectToDatabase e a criação dos índices. Em resumo: inclua essas três linhas no topo da função startServer.
A partir daqui, podemos focar na lógica da API.
Defina os tipos GraphQL, resolvers e a lógica da aplicação
Já definimos os tipos TypeScript para trabalhar com o banco e trafegar dados na aplicação. Falta criar as definições de tipos do GraphQL, que determinam o que o usuário pode fazer na API.
O tipo User define o que o cliente pode solicitar em uma consulta. Neste exemplo, ele espelha a interface User, mas não precisa ser assim. Por exemplo, você pode ter um campo como senha que nunca deve ser retornado ao cliente.
O tipo Query define as consultas que podem ser executadas na API. Cada uma retorna um User ou um array de User, e podemos ter lógica específica entre elas.
O tipo Mutation é semelhante ao Query, mas voltado a operações que manipulam dados.
Com as definições de tipos criadas, podemos focar nos resolvers da aplicação. No arquivo src/resolvers/index.ts, inclua o seguinte:
Essa query usa o UserService para buscar todos os usuários. A resposta User[] é enviada ao cliente, mas apenas com os campos solicitados e que existirem nas definições de tipo GraphQL.
Por exemplo, suponha que o cliente envie esta consulta:
No exemplo acima, temos mais campos disponíveis, mas o cliente quer apenas name e email. Isso é controlado pelas definições de tipo GraphQL com as quais o cliente trabalha. O Apollo faz a mágica acontecer.
As mesmas regras se aplicam a cada query e mutation no arquivo **src/resolvers/index.ts**.
createUser:async(
_:unknown,{ name, email, age }: CreateUserInput
):Promise<User>=>{const userService =newUserService();returnawait userService.createUser({ name, email, age });},Tecnologia
Na definição de createUser, vimos que o cliente pode enviar name, email e age. No resolver createUser, fazemos o mesmo e chamamos a função createUser do serviço de banco. A resposta volta ao cliente com os campos que ele pediu.
Compile e rode a API GraphQL
A essa altura, a API GraphQL deve estar redonda. Existem algumas formas de compilar e rodar a aplicação.
Por exemplo, você pode adicionar o seguinte ao **package.json**:
Seguindo esse caminho, rode npm run build para compilar e npm run start para iniciar o servidor. A porta padrão é 3000 e você pode testar com cURL ou a ferramenta de sua preferência.
Conclusão
Você acabou de ver como criar uma API GraphQL com TypeScript e MongoDB como banco. Embora o exemplo seja simples, vimos como construir queries e mutations para realizar operações CRUD no banco. O MongoDB é um par perfeito para APIs GraphQL porque o formato de dados se mantém semelhante do começo ao fim. Por exemplo, o cliente executa uma consulta que se parece com JSON. Os dados que trafegam pela consulta exigem pouca formatação e ajustes — e o mesmo vale na ida e volta ao banco. A experiência é fluida, permitindo focar mais no usuário e menos em manipular dados para satisfazer o banco.
GraphQL é ótimo porque você consulta uma API como consultaria um banco de dados, pedindo só o que precisa em retorno. Isso torna tudo mais leve e eficiente para o cliente.
Is Apollo the only option for GraphQL?⌄
Apollo facilita muito a criação de APIs GraphQL, mas não é a única opção. No entanto, as outras alternativas fogem ao escopo deste tutorial.
Why use MongoDB with GraphQL?⌄
Ambos trabalham com formatos de dados semelhantes a JSON, então você não precisa ficar convertendo entre estruturas diferentes ao mover dados do banco para a API e para o cliente.
What does a resolver do?⌄
Um resolver é a função que diz ao GraphQL como buscar ou modificar os dados de um campo específico do seu schema.
What's the difference between a query and a mutation?⌄
Queries servem para ler/buscar dados (como requisições GET), enquanto mutations servem para criar, atualizar ou excluir dados (como POST, PUT, DELETE).
Autor
Nic Raboy
Nic Raboy é Líder de Relações com Desenvolvedores na MongoDB, onde lidera uma equipe de desenvolvedores Python, Java, C# e PHP que criam conteúdo incrível para ajudar os desenvolvedores a serem bem-sucedidos na inclusão do MongoDB em seus projetos. Ele tem experiência com Golang e JavaScript e escreve com frequência sobre muitas de suas aventuras de desenvolvimento.
Descubra para que servem os bancos de dados NoSQL, por que os cientistas de dados os utilizam e uma lista dos melhores bancos de dados NoSQL disponíveis.
Este guia completo ensina os fundamentos do desenvolvimento backend com Python. Aprenda conceitos básicos, frameworks e boas práticas para começar a criar aplicações web.
Aprenda a trabalhar com JSON em Python, incluindo serialização, deserialização, formatação, otimização de desempenho, manipulação de APIs e compreensão das limitações e alternativas do JSON.
GraphQL é uma forma poderosa e eficiente de construir APIs. O cliente consulta a API como consultaria um banco de dados, e a API retorna apenas os dados solicitados, geralmente reduzindo o payload e melhorando o tempo de resposta. E tempos de resposta baixos são críticos no mundo atual.
Quando a API GraphQL é combinada com MongoDB, além da rapidez, você ganha um formato de dados consistente de ponta a ponta.
Imagine o seguinte: seu cliente executa uma consulta GraphQL que se parece com JSON. Quando esses dados chegam à sua aplicação — digamos que seja em TypeScript — você continua trabalhando com um formato semelhante a JSON na aplicação. Indo além, ao usar MongoDB, os dados enviados e recebidos do banco também são parecidos com JSON. Ou seja, você tem uma experiência de dados consistente com alto desempenho. Menos preocupação em manipular e formatar dados e mais foco na experiência do usuário da aplicação, sem se prender ao banco e às ferramentas.
Neste tutorial, você vai ver como é simples usar MongoDB na sua API GraphQL, desta vez construída com TypeScript.
Pré-requisitos
Para este tutorial, garanta que você tem o seguinte pronto para uso:
Não faremos nada muito elaborado no banco neste tutorial, então qualquer tier do cluster MongoDB funciona, inclusive o gratuito. Contudo, não vamos cobrir o provisionamento de um cluster MongoDB Atlas aqui. A expectativa é que você já tenha regras de usuário e de rede configuradas. Se precisar de ajuda para começar, confira a documentação Get Started With Atlas sobre o tema.
Você vai precisar ter uma aplicação Node.js criada. Se preferir, execute os comandos abaixo:
Os comandos acima criam um novo diretório de projeto e um package.json básico. Usaremos TypeScript, Express, MongoDB e Apollo neste projeto. Instale tudo com os comandos a seguir:
Eles instalam as dependências e as definições de tipos para uso com TypeScript.
Por fim, crie um arquivo de configuração do TypeScript. Na raiz do projeto, crie o tsconfig.json com o JSON abaixo:
Se em algum momento você quiser ver o projeto completo ou testá-lo, confira no GitHub.
Crie a base com Apollo, Express e dependências do MongoDB
Em termos de estrutura, vamos usar os seguintes arquivos:
O arquivo src/index.ts terá todo o bootstrap do Express e do Apollo. Ou seja, iniciar o servidor, definir os endpoints GraphQL etc. O arquivo src/types/index.ts conterá as definições TypeScript para trabalhar com o modelo de dados do banco dentro da aplicação. Não confunda com o src/schema/typeDefs.ts, que, apesar de parecido, foca nas definições de tipos do GraphQL.
Isso nos deixa com os diretórios database e resolvers. A maior parte da lógica da aplicação e do banco ficará neles, como veremos já já.
Por agora, vamos configurar o servidor no arquivo src/index.ts:
Vamos ampliar esse arquivo conforme avançarmos.
Para começar, importamos todas as dependências do projeto. Em seguida, configuramos o servidor Express e o middleware do Apollo.
Ainda não adicionamos nada aos arquivos src/schema/typeDefs.ts e src/resolvers/index.ts, mas já deixamos tudo pronto. O servidor não vai subir sem erros até preenchermos essas partes.
Vale mencionar que este exemplo permite requisições de todas as origens por causa do seguinte:
Isso é bom para desenvolvimento, mas em produção limite às origens necessárias.
Interagindo com o MongoDB a partir da aplicação TypeScript com Node.js
Apesar de já termos o Express e o Apollo configurados, ainda não conectamos o MongoDB na aplicação. Antes de ir ao código, adicione as informações de conexão no arquivo .env:
Substitua
<USERNAME>,<PASSWORD>e<HOST>pelas informações do seu cluster no dashboard do MongoDB Atlas.Agora, vamos estabelecer a lógica de conexão do banco. No arquivo **src/database/connection.ts**, adicione o código abaixo:
A ideia aqui é criar uma conexão singleton. Existem várias formas de usar o MongoDB na sua aplicação, mas escolhemos este caminho para este projeto.
A função
connectToDatabaselê as informações do arquivo **.env**, conecta e retorna o banco definido. Se falhar, o erro é capturado e propagado. Você só deve precisar dessa função na configuração inicial do servidor, como vimos no arquivo anterior.A função
getDatabaseserá usada sempre que uma requisição chegar à aplicação. Ela retorna a instância atual da conexão.Para manter tudo organizado, temos a função
closeDatabasepara encerrar a conexão quando o servidor terminar. É opcional, mas uma boa prática.Antes da lógica de banco, vale definir nossos tipos TypeScript para os DTOs (data transfer objects):
As interfaces acima, no arquivo **src/types/index.ts**, são gerenciadas pela nossa API, mas a lógica de banco também precisa conhecê-las. Você verá já já.
Isso nos leva às interações reais de banco para criar, ler, atualizar e excluir (CRUD), feitas no arquivo src/database/userService.ts. A ideia é: os resolvers do GraphQL tratam a lógica da aplicação e chamam as funções deste serviço, e o serviço cuida da lógica do banco.
No arquivo src/database/userService.ts, temos o seguinte código:
A primeira coisa a notar é a interface abaixo:
Ela é parecida com a interface
User, mas não idêntica.UserDocumentmodela mais estritamente como o banco trabalha. No banco usamosObjectId, mas na aplicação usamos strings. No MongoDB, o id é_id, nãoid, como na interfaceUser.Isso nos leva à classe
UserService.Depois de obter o handle da coleção a partir da instância do banco no
constructor, temos a seguinte série de funções:A função
toUseré um helper para converter entreUsereUserDocument. Enviamos e recebemosUserDocumentdo banco, e precisamos converter paraUserao expor pela aplicação.A função
createIndexescria alguns índices de exemplo que podem ser úteis. Por padrão, existe índice apenas em_id, o que basta para um demo pequeno, mas conforme sua aplicação cresce, não deixe de criar índices adequados aos seus dados e consultas. Os dois acima são apenas exemplos.Agora podemos olhar cada função do serviço, começando por
getAllUsers:Essa função busca todos os documentos da coleção, pois não há filtro em
find. Os resultados são convertidos comtoUsere retornados para a aplicação, que serão usados pelos resolvers do Apollo.A função
getByUserIdé parecida, mas agora usamosfindOnecom um critério de filtro — neste caso, buscar um único usuário pelo_id, que é único. Como oidemUseré string, precisamos envolvê-lo emObjectIdpara a consulta no MongoDB.Agora, criar dados no MongoDB com
createUser:Após usar
insertOnepara inserir, usamos oinsertedIdretornado para consultar o registro. Fazemos isso porque, em uma API GraphQL, ao criar dados, o esperado é já poder consultá-los na mesma mutation.A função
updateUseré um pouco diferente:Nesta aplicação, permitimos atualização apenas dos campos
name,emaileage. Todos são opcionais, então só os incluímos no critério de atualização se estiverem presentes.Isso nos leva ao
findOneAndUpdate.O primeiro objeto é o filtro. Só queremos atualizar quando o
_idcorresponder. Para cada correspondência, usamos o operador$setpara substituir ou criar os campos emupdateFields. Campos ausentes não são alterados. Por fim, como estamos encontrando e atualizando, definimosreturnDocumentcomoafterpara receber o documento já alterado. Se usássemosupdateOne, teríamos apenas informações da operação, não os dados.A última operação de CRUD é remover dados do MongoDB com
deleteUser:A função
deleteOnese assemelha àfindOnepois usa o filtro por_id. A diferença é que qualquer correspondência — no máximo um documento — será removida.Agora a lógica de banco está pronta. Para finalizar, conecte-a a partir de **src/index.ts**:
No trecho acima, omitimos a maior parte do src/index.ts. A ideia é só mostrar o uso de
connectToDatabasee a criação dos índices. Em resumo: inclua essas três linhas no topo da funçãostartServer.A partir daqui, podemos focar na lógica da API.
Defina os tipos GraphQL, resolvers e a lógica da aplicação
Já definimos os tipos TypeScript para trabalhar com o banco e trafegar dados na aplicação. Falta criar as definições de tipos do GraphQL, que determinam o que o usuário pode fazer na API.
No arquivo src/schema/typeDefs.ts, adicione:
O tipo
Userdefine o que o cliente pode solicitar em uma consulta. Neste exemplo, ele espelha a interfaceUser, mas não precisa ser assim. Por exemplo, você pode ter um campo como senha que nunca deve ser retornado ao cliente.O tipo
Querydefine as consultas que podem ser executadas na API. Cada uma retorna umUserou um array deUser, e podemos ter lógica específica entre elas.O tipo
Mutationé semelhante aoQuery, mas voltado a operações que manipulam dados.Com as definições de tipos criadas, podemos focar nos resolvers da aplicação. No arquivo src/resolvers/index.ts, inclua o seguinte:
Os resolvers acima estendem o que vimos nas definições de tipos para queries e mutations.
Veja a query
users:Essa query usa o
UserServicepara buscar todos os usuários. A respostaUser[]é enviada ao cliente, mas apenas com os campos solicitados e que existirem nas definições de tipo GraphQL.Por exemplo, suponha que o cliente envie esta consulta:
No exemplo acima, temos mais campos disponíveis, mas o cliente quer apenas
nameeemail. Isso é controlado pelas definições de tipo GraphQL com as quais o cliente trabalha. O Apollo faz a mágica acontecer.As mesmas regras se aplicam a cada query e mutation no arquivo **src/resolvers/index.ts**.
Na definição de
createUser, vimos que o cliente pode enviarname,emaileage. No resolvercreateUser, fazemos o mesmo e chamamos a funçãocreateUserdo serviço de banco. A resposta volta ao cliente com os campos que ele pediu.Compile e rode a API GraphQL
A essa altura, a API GraphQL deve estar redonda. Existem algumas formas de compilar e rodar a aplicação.
Por exemplo, você pode adicionar o seguinte ao **package.json**:
Seguindo esse caminho, rode
npm run buildpara compilar enpm run startpara iniciar o servidor. A porta padrão é 3000 e você pode testar com cURL ou a ferramenta de sua preferência.Conclusão
Você acabou de ver como criar uma API GraphQL com TypeScript e MongoDB como banco. Embora o exemplo seja simples, vimos como construir queries e mutations para realizar operações CRUD no banco. O MongoDB é um par perfeito para APIs GraphQL porque o formato de dados se mantém semelhante do começo ao fim. Por exemplo, o cliente executa uma consulta que se parece com JSON. Os dados que trafegam pela consulta exigem pouca formatação e ajustes — e o mesmo vale na ida e volta ao banco. A experiência é fluida, permitindo focar mais no usuário e menos em manipular dados para satisfazer o banco.
Se você travar em algum ponto, confira o projeto finalizado no GitHub.