- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
Recentemente eu desenvolvi o desafio da Woovi com Koa.js e GraphQL (). Uma das especificações opcionais era o uso do graphql-http, substituto do koa-graphql, que está presente no disponibilizado. No entanto, me deparei com alguns problemas iniciais: diversas funcionalidades não estavam presentes no graphql-http, visto que ele é apenas uma , isto é, é simples e minimalista.
Introdução - Por quê? E alguns problemas
Primeiro, por quê? Bom, principalmente, porque koa-graphql foi e graphql-http deve ser utilizado ao invés disso. Tomei esta decisão baseada em uma que inclusive recomendava o uso do yoga, uma alternativa mais completa para servidores graphql, no entanto, eu vi que seria completamente capaz de resolver apenas com graphql-http. Caso queira uma opção mais robusta, tente o .
Vamos aos problemas. Eu utilizava algumas funcionalidades interessantes já presentes no koa-graphql, como:
A primeira coisa a ser feita é remover koa-graphql completamente.
pnpm un koa-graphql
E adicionar graphql-http:
pnpm i graphql-http
Com isso, é começar a adaptar as funcionalidades.
Criando o handler
Seguindo o snippet que está presente no próprio README do graphql-http, para criar o handler é bem simples, é só importar o createHandler para o koa e criar passar o schema e o context:
import { createHandler } from 'graphql-http/lib/use/koa';
const graphqlHandler = createHandler({
schema,
context: async (ctx) => {
const { user } = await getUser(ctx);
return getContext({ ctx, user });
},
});
Ok, o schema é a parte mais fácil, apenas passei o schema definido anteriormente. No entanto, tive alguns problemas com os tipos do context, especialmente porque a forma de conseguir as informações do header pode ser diferentes.
Adaptando os tipos do context
No get-context.ts, criei o tipo que deveria receber para o contexto:
import type { Request } from 'graphql-http';
import type { IncomingMessage } from 'node:http';
import type { RequestContext } from 'graphql-http/lib/use/koa';
export type RequestGraphQLContext = Request<IncomingMessage, RequestContext>;
Agora para o get-user.ts, antes eu simplesmente pegava o valor de context.headers.authorization, mas agora a tipagem para o headers é completamente diferente:
Portanto, criei uma função para validar corretamente, o get-graphql-http-headers.ts:
import { Request } from 'graphql-http';
import { RequestContext } from 'graphql-http/lib/use/koa';
import { IncomingMessage } from 'node:http';
type HeaderKeyType = 'authorization';
export function getGraphQLHttpHeaders(
ctx: Request<IncomingMessage, RequestContext>,
headerKey: HeaderKeyType
) {
if (typeof ctx.headers.get === 'function') {
return ctx.headers.get(headerKey);
}
if (typeof ctx.headers === 'object') {
const header = (
ctx.headers as Record<string, string | string[] | undefined>
)[headerKey];
if (Array.isArray(header)) {
return header[0] ?? null;
} else if (typeof header === 'string') {
return header;
}
}
return null;
}
Agora, para conseguir o authToken, basta utilizar a função criada:
const authHeader = getGraphQLHttpHeaders(ctx, 'authorization');
Schema e Context prontos! Agora faltam as funcionalidades: graphiql e tratamento de erros.
Tratamento de erros
Agora ficou ainda mais simples! Não precisamos passar uma função para o handler novo, basta apenas adicionar um middleware!
// graphql error handling middleware
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
if (error instanceof GraphQLError) {
if (logEnvironments.includes(config.NODE_ENV)) {
console.log(error.message);
console.log(error.locations);
console.log(error.stack);
}
ctx.body = {
errors: [
{
message: error.message,
locations: error.locations,
stack: config.NODE_ENV === 'development' ? error.stack : undefined,
},
],
};
} else {
throw error;
}
}
});
GraphiQL - playground
Aqui eu mudei um pouco a forma que o playground é acessado. Como ele é utilizado apenas em ambiente de desenvolvimento, resolvi utilizar o e adicionar um script no package.json do server para acessá-lo. Para isto, ele pede que o pacote ruru seja instalado, este inclusive pode ser utilizado em qualquer endpoint graphql disponível!
npx ruru -SP -p 3001 -e
Acabou! Com isso, poupei alguns KBs:
E, principalmente, atualizei para uma lib mais atualizada.
No meu projeto no você pode visualizar exatamente como foi minha linha de pensamento visualizando a e o . E outra: tem um bônus onde eu configurei o graphiql em um app react-router v7 (antigo Remix), vale a pena dar uma olhada!
Caso tenha alguma dúvida, não hesite em comentar ou me chamar em qualquer um dos meus
Referencias
Foto de na
Introdução - Por quê? E alguns problemas
Primeiro, por quê? Bom, principalmente, porque koa-graphql foi e graphql-http deve ser utilizado ao invés disso. Tomei esta decisão baseada em uma que inclusive recomendava o uso do yoga, uma alternativa mais completa para servidores graphql, no entanto, eu vi que seria completamente capaz de resolver apenas com graphql-http. Caso queira uma opção mais robusta, tente o .
Vamos aos problemas. Eu utilizava algumas funcionalidades interessantes já presentes no koa-graphql, como:
- o uso de uma customErrorFn para retornar erros do servidor de uma maneira específica, além de logar no console em ambientes de teste e desenvolvimento
- graphiql: um playground GraphQL para fazer queries e mutations de maneira intuitiva pelo navegador
- alterar o context da requisição, já que o projeto armazenava o usuário caso estivesse logado (por meio de token JWT)
A primeira coisa a ser feita é remover koa-graphql completamente.
pnpm un koa-graphql
E adicionar graphql-http:
pnpm i graphql-http
Com isso, é começar a adaptar as funcionalidades.
Criando o handler
Seguindo o snippet que está presente no próprio README do graphql-http, para criar o handler é bem simples, é só importar o createHandler para o koa e criar passar o schema e o context:
import { createHandler } from 'graphql-http/lib/use/koa';
const graphqlHandler = createHandler({
schema,
context: async (ctx) => {
const { user } = await getUser(ctx);
return getContext({ ctx, user });
},
});
Ok, o schema é a parte mais fácil, apenas passei o schema definido anteriormente. No entanto, tive alguns problemas com os tipos do context, especialmente porque a forma de conseguir as informações do header pode ser diferentes.
Adaptando os tipos do context
No get-context.ts, criei o tipo que deveria receber para o contexto:
import type { Request } from 'graphql-http';
import type { IncomingMessage } from 'node:http';
import type { RequestContext } from 'graphql-http/lib/use/koa';
export type RequestGraphQLContext = Request<IncomingMessage, RequestContext>;
Agora para o get-user.ts, antes eu simplesmente pegava o valor de context.headers.authorization, mas agora a tipagem para o headers é completamente diferente:
- é preciso utilizar context.headers.get
- get pode ser uma função, uma string, ou conter array no seu valor
Portanto, criei uma função para validar corretamente, o get-graphql-http-headers.ts:
import { Request } from 'graphql-http';
import { RequestContext } from 'graphql-http/lib/use/koa';
import { IncomingMessage } from 'node:http';
type HeaderKeyType = 'authorization';
export function getGraphQLHttpHeaders(
ctx: Request<IncomingMessage, RequestContext>,
headerKey: HeaderKeyType
) {
if (typeof ctx.headers.get === 'function') {
return ctx.headers.get(headerKey);
}
if (typeof ctx.headers === 'object') {
const header = (
ctx.headers as Record<string, string | string[] | undefined>
)[headerKey];
if (Array.isArray(header)) {
return header[0] ?? null;
} else if (typeof header === 'string') {
return header;
}
}
return null;
}
Agora, para conseguir o authToken, basta utilizar a função criada:
const authHeader = getGraphQLHttpHeaders(ctx, 'authorization');
Schema e Context prontos! Agora faltam as funcionalidades: graphiql e tratamento de erros.
Tratamento de erros
Agora ficou ainda mais simples! Não precisamos passar uma função para o handler novo, basta apenas adicionar um middleware!
// graphql error handling middleware
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
if (error instanceof GraphQLError) {
if (logEnvironments.includes(config.NODE_ENV)) {
console.log(error.message);
console.log(error.locations);
console.log(error.stack);
}
ctx.body = {
errors: [
{
message: error.message,
locations: error.locations,
stack: config.NODE_ENV === 'development' ? error.stack : undefined,
},
],
};
} else {
throw error;
}
}
});
GraphiQL - playground
Aqui eu mudei um pouco a forma que o playground é acessado. Como ele é utilizado apenas em ambiente de desenvolvimento, resolvi utilizar o e adicionar um script no package.json do server para acessá-lo. Para isto, ele pede que o pacote ruru seja instalado, este inclusive pode ser utilizado em qualquer endpoint graphql disponível!
npx ruru -SP -p 3001 -e
- -p especifica a porta do playground
- -e especifica a rota para o servidr graphql
Acabou! Com isso, poupei alguns KBs:
E, principalmente, atualizei para uma lib mais atualizada.
No meu projeto no você pode visualizar exatamente como foi minha linha de pensamento visualizando a e o . E outra: tem um bônus onde eu configurei o graphiql em um app react-router v7 (antigo Remix), vale a pena dar uma olhada!
Caso tenha alguma dúvida, não hesite em comentar ou me chamar em qualquer um dos meus
Referencias
- Meu projeto no GitHub:
Foto de na