Recentemente escrevi um tutorial ensinando os fundamentos do GraphQL para iniciantes no assunto. Eu trouxe a teoria básica e fizemos um pequeno projeto de API GraphQL usando Node.js e Express, você confere o conteúdo neste link. Nesta segunda parte, quero entrar mais a fundo em alguns aspectos desse tipo de API, trazendo conceitos que você provavelmente já conhece no REST, mas na sua versão GraphQL.
Um ponto de atenção é que não vou usar banco de dados aqui, apenas estruturas em memória, mas o código tranquilamente poderia ser adaptado para cenários com banco de dados sejam eles quais forem, a critério do desenvolvedor.
Vamos lá!
#1 – Cadastro em GraphQL
Para entrarmos em assuntos mais elaborados, que tal transformarmos nossa API GraphQL em um CRUD?
Antes disso, ajuste seu resolvers.js para ter um array de usuários e já deixar pré-cadastro 100 deles com o código abaixo logo no topo do arquivo. Repare no padrão de usuário e senha, isso será útil nos testes, bem como os ids auto-incrementais.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const users = []; let nextId = 0; for (let i = 0; i < 100; i++) { users.push({ id: (nextId++).toString(), name: `User${i}`, email: `user${i}@example.com`, password: `password${i}` }); } |
Agora para permitirmos o cadastro de um novo usuário, crie no resolvers.js a seguinte função, que é auto-explicativa:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
createUser({ userInput }, req) { //validações aqui (Joi, por exemplo) const user = { id: Math.random().toString(), name: userInput.name, email: userInput.email, password: userInput.password }; users.push(user); return user; }, |
E por fim, no schema.js, você vai precisar atualizar o type RootMutation para incluir essa e outras duas possibilidades que vamos precisar (update e delete):
|
1 2 3 4 5 6 7 |
type RootMutation { createUser(userInput: UserInputData): User! updateUser(id: ID!, userUpdateData: UserUpdateData): User! deleteUser(id: ID!): User! } |
Lembrando que esse type RootMutation deve estar referenciado no schema raiz do GraphQL:
|
1 2 3 4 5 6 |
schema { query: RootQuery, mutation: RootMutation } |
Agora para testar, você pode usar o Postman. Crie uma request nova do tipo GraphQL e informe o endereço da sua API, após deixar ela rodando. Você vai ver que o Postman vai carregar automaticamente as suas queries e mutations no painel da esquerda, então basta escolher a createUser, marcar que você quer passar userInput e os campos que deseja no retorno. Veja (ignore se você não tiver todas funções ainda):

Com isso, no painel à direita algumas coisas já vão vir preenchidas. No campo de cima, você deve informar os detalhes da requisição à mutation. Note abaixo como usei uma variável para o userInput, isso será útil a seguir.
|
1 2 3 4 5 6 7 8 9 |
mutation CreateUser($userInput: UserInputData) { createUser(userInput: $userInput) { id name email } } |
Agora, no campo de baixo, definimos as variáveis que vamos usar:
|
1 2 3 4 5 6 7 8 9 |
Ao enviar a requisição, você terá como resultado o novo usuário criado e salvo em memória.

#2 – Consultando em GraphQL
Agora indo para a consulta de um usuário por id, nosso resolver fica dessa forma:
|
1 2 3 4 5 6 7 8 9 |
getUser({ id }, req) { const user = users.find(u => u.id === id); if (!user) { throw new Error("User not found."); } return user; }, |
Enquanto nosso schema RootQuery fica assim, já incluindo outras queries que vamos ter mais à frente:
|
1 2 3 4 5 6 7 |
type RootQuery { getUsers(page: Int, pageSize: Int): [User!]! getUser(id: ID!): User! login(email: String!, password: String!): AuthData! } |
Agora quando for testar no Postman, basta selecionar a getUser no painel esquerdo. Repare como tem um campo ao lado do parâmetro de id, para você informar qual id deseja usar como filtro na pesquisa. Isso vai fazer com que a query no painel direito já se ajuste corretamente, bastando enviá-la.

Se você informar o id de um usuário existente, ele será retornado com sucesso, caso contrário terá um erro. Mas e se eu quiser fazer uma listagem de usuários? Vamos voltar ao resolvers.js para adicionar a getUsers, que contempla paginação.
|
1 2 3 4 5 6 7 8 9 |
getUsers({ page, pageSize }, req) { page = page > 1 ? page : 1; pageSize = pageSize > 10 ? pageSize : 10; const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; return users.slice(startIndex, endIndex); }, |
Assim, ao testar em uma nova request GraphQL no Postman, se você não passar os parâmetros page e pageSize, eles terão os valores default (1 e 10, respectivamente), caso contrário, você terá como resultado exatamente os usuários que deseja ver, como no exemplo abaixo onde aparece os dois primeiros usuários da página 2, sendo que estou pegando 10 elementos por página.


#3 – Editando e Excluindo em GraphQL
As demais operações não tem nada de especial a ser explicado, então podemos passar por elas rapidamente. A edição de um usuário pode ser feita em uma nova função no resolver como abaixo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
updateUser({ id, userUpdateData }, req) { const userIndex = users.findIndex(u => u.id === id); if (userIndex < 0) { throw new Error("User not found."); } const user = users[userIndex]; user.name = userUpdateData.name || user.name; user.email = userUpdateData.email || user.email; user.password = userUpdateData.password || user.password; return user; }, |
O schema nós já deixamos preparado anteriormente. Repare que nele esperamos um ID (do usuário a ser editado) e passamos os novos dados que queremos substituir, sendo que nenhum campo é obrigatório. Note também que no Postman podemos usar somente o painel à esquerda, preenchendo os campos de acordo.

Ao enviar essa requisição, o usuário de id informado será atualizado de acordo.
Agora a exclusão necessita de outra função no resolver, como abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 |
deleteUser({ id }, req) { const userIndex = users.findIndex(u => u.id === id); if (userIndex < 0) { throw new Error("User not found."); } const user = users[userIndex]; users.splice(userIndex, 1); return user; } |
E o teste é ainda mais simples que os anteriores pois precisamos informar apenas um id de um usuário existente.

#4 – Melhorias Gerais
Além do desenvolvimento do CRUD em si, podemos aplicar algumas melhorias gerais em nossa API GraphQL. Por exemplo, para permitir que seja possível a chamada por frontends mais tarde, você deve adicionar o seguinte middleware no Express que trata do verbo OPTIONS. Request com este verbo costumam ser chamadas automaticamente pelo frontend e o GraphQL vai dar erro se não tratarmos elas antes de chegar nele.
|
1 2 3 4 5 6 7 8 |
app.options("/graphql", (req, res) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.sendStatus(200); }); |
Para que nosso backend também possa ser usado por frontends diversos, precisamos configurar a política de CORS nele, primeiro instalando o pacote específico.
|
1 2 3 |
npm install cors |
E adicionando no index.js antes do app.options. Da forma como deixei abaixo, a política é universal, mas você pode ajustar de acordo com a sua necessidade (por domínio).
|
1 2 3 4 |
import cors from "cors"; app.use(cors()); |
Outra sugestão de melhoria é relacionada ao tratamento de erros. Por padrão o GraphQL vai retornar um objet0 com uma propriedad data: null e um errors contendo um array de erros. Mas não precisa ser só isso. Você pode sobrescrever o comportamento padrão de error handling para poder lidar com erros personalizados. Por exemplo, vamos dizer que quando tivermos erro de usuário não encontrado, queremos além de ter a mensagem de erro, um código e dados adicionais, como abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
getUser({ id }, req) { const user = users.find(u => u.id === id); if (!user) { const error = new Error("User not found."); error.code = 404; error.data = { id }; throw error; } return user; }, |
Para que tenhamos acesso a essas informações adicionais e exibamos elas corretamente, devemos sobrescrever o handler de erro lá no index.js, como abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
app.use("/graphql", graphqlHTTP({ schema, rootValue: resolvers, graphiql: true, customFormatErrorFn(err) { if(!err.originalError) { return err; } return { message: err.originalError.message, data: err.originalError.data, code: err.originalError.code }; } })); |
A função customFormatErrorFn é responsável por tratar os erros disparados dentro das queries GraphQL e ela recebe um objeto de erro. Caso esse objeto não tenha a propriedade originalError populada, quer dizer que ele é um erro de programação, não foi jogado intencionalmente por você. Nesse caso, retorná-lo faz com que aconteça o comportamento normal com erros que já conhecemos.
Agora se tivermos um originalError, ou seja, disparo via throw, então podemos pegar os seus campos e usá-los para devolver algo mais personalizado ao usuário. Veja o resultado abaixo quando pesquiso um usuário que não existe.

#5 – Autenticação
Por fim, vamos falar agora de autenticação, afinal, na maior parte das vezes você não vai querer expor a API da sua empresa ou do seu cliente publicamente, para qualquer um sair acessando e mutando os dados. Para isso, vamos criar uma um resolver de login.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
login({ email, password }, req) { const user = users.find(u => u.email === email); const error = new Error("Invalid user and/or password"); error.code = 401; if (!user) throw error; if (user.password !== password) throw error; return { token: "dummy-token" }; }, |
Eu não vou implementar aqui um fluxo de login completo, com criptografia e emissão de tokens. Possivelmente você já fez isso em APIs REST e pode usar exatamente os mesmos padrões como Bcrypt para senhas e JWT para tokens, por exemplo, já que estamos falando novamente de APIs stateless e independente de client. Então a minha autenticação será uma comparação simples em texto plano mesmo, em cima dos usuários guardados no array em memória. Caso o usuário ou senha seja inválido, eu jogo um erro padrão de não autorizado. Caso contrário, devolvo um token de exemplo.
Agora no schema, devemos adicionar no RootQuery.
|
1 2 3 4 5 6 7 |
type RootQuery { getUsers(page: Int, pageSize: Int): [User!]! getUser(id: ID!): User! login(email: String!, password: String!): AuthData! } |
Teste o fluxo via Postman antes de avançar, tanto o cenário de sucesso na autenticação, quanto de falha.
Agora vamos criar o middleware de autenticação, que vai verificar se o token foi recebido nas requisições e se ele é válido. Para isso, crie um arquivo auth.js na raiz do projeto, como abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
export default (req, res, next) => { const authHeader = req.get("Authorization"); if (!authHeader) { req.isAuth = false; return next(); } const authHeaderParts = authHeader.split(" "); const token = authHeader.split(" ")[authHeaderParts.length - 1]; if(token !== "dummy-token") { req.isAuth = false; return next(); } req.isAuth = true; req.userId = token;//extract the token data here (jwt, for example) next(); } |
Note que não há nada muito diferente do que já fazemos em APIs com Express no geral. Se não tiver token ou se ele não for o que esperamos, marcamos req.isAuth como false e executamos o próximo middleware. Caso contrário, marcamos req.isAuth como true e decodificamos o token para pegar dados do usuário que fez a requisição. Importante frisar: esse middleware não nega requisições, apenas marca elas como autenticadas ou não. Essa responsabilidade vai ficar com o resolver.
Esse middleware deve ser plugado em nosso server Express antes da rota do GraphQL, como abaixo.
|
1 2 3 4 |
import auth from "./auth.js"; app.use(auth); |
Para finalizar, temos de fazer com que as queries e mutations que queremos privadas verifiquem se a requisição foi autenticada corretamente pelo middleware, como abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
deleteUser({ id }, req) { if(!req.isAuth) { const error = new Error("Unauthorized"); error.code = 401; error.token = req.headers["Authorization"]; throw error; } const userIndex = users.findIndex(u => u.id === id); if (userIndex < 0) { const error = new Error("User not found."); error.code = 404; error.data = { id }; throw error; } const user = users[userIndex]; users.splice(userIndex, 1); return user; } |
Agora, experimente tentar excluir um usuário sem passar o token e verá que não é mais possível. Somente indo na aba Authorization (ou Headers) do Postman e informando o token correto que você conseguirá.

E com isso finalizamos mais este tutorial de GraphQL com Express.

Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.

