Usar Apollo Server no Node.js permite criar APIs GraphQL com schema, resolvers, context, plugins, cache e integrações com frameworks web. O servidor é compatível com clientes GraphQL e pode funcionar como API única, subgraph de Federation ou camada de composição sobre bancos e serviços.
Uma implementação de produção precisa ir além do exemplo inicial. É necessário autenticar no context, aplicar autorização em resolvers, limitar profundidade e complexidade, evitar N+1, configurar CORS e CSRF, padronizar erros, drenar o servidor no shutdown e observar operações.
Neste guia, você aprenderá a iniciar Apollo Server, definir schema, criar resolvers, integrar com Express, usar DataLoader, validar acesso, aplicar plugins, tratar erros, testar e preparar o deploy.
O que é Apollo Server?
Apollo Server é um servidor GraphQL open source para Node.js. A documentação oficial do Apollo Server apresenta as versões atuais e integrações. A especificação GraphQL define linguagem, validação e execução.
Quando usar?
- API GraphQL central;
- BFF para web ou mobile;
- subgraph federado;
- composição de bancos e REST;
- migração gradual de APIs;
- portal interno de dados.
Instalando
npm install @apollo/server graphqlPara Express, instale também a integração e os middlewares necessários conforme a versão.
Schema
const typeDefs = `#graphql
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
}
`;O schema é o contrato público. Campos nullable e non-null possuem impacto no tratamento de erros.
Resolvers
const resolvers = {
Query: {
user: (_, { id }, context) => {
return context.repositories.users.findById({
tenantId: context.auth.tenantId,
userId: id
});
}
}
};Resolvers devem ser pequenos e delegar regras a serviços e repositories.
Iniciando standalone
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const server = new ApolloServer({
typeDefs,
resolvers
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => createContext(req)
});Standalone é útil para projetos simples. Integrações com framework oferecem mais controle de middleware, cookies e rotas.
Integração com Express
import express from 'express';
import cors from 'cors';
import { json } from 'body-parser';
import { expressMiddleware } from '@apollo/server/express4';
await server.start();
app.use(
'/graphql',
cors(corsOptions),
json({ limit: '1mb' }),
expressMiddleware(server, {
context: async ({ req }) => createContext(req)
})
);Confirme o pacote de integração indicado pela versão atual.
Context
async function createContext(req) {
const auth = await authenticate(req);
return {
auth,
repositories,
services,
loaders: createLoaders({ auth, repositories }),
requestId: req.headers['x-request-id']
};
}Crie um novo context por operação.
Não coloque estado global
Usuário, tenant e loaders não podem ficar em variáveis globais, pois requisições executam concorrentemente.
Autenticação
Valide token ou sessão antes de executar operações protegidas. Um context pode representar usuário anônimo, mas resolvers precisam exigir identidade quando necessário.
Consulte JWT Seguro no Node.js e Sessões Seguras no Node.js.
Autorização
function requirePermission(context, permission) {
if (!context.auth.permissions.includes(permission)) {
throw new ForbiddenError();
}
}Não confie na ausência do campo na interface. A regra deve estar no backend.
Veja RBAC no Node.js e ABAC no Node.js.
Autorização por objeto
Uma permission geral não garante acesso ao recurso. Inclua tenant e ownership na consulta.
Multi-tenancy
O tenant vem da identidade, não de argumento livre. Consulte Multi-Tenancy no Node.js.
N+1
Resolvers de campos relacionados podem executar uma consulta por item. Use DataLoader no Node.js.
Loader por request
function createLoaders({ auth, repositories }) {
return {
user: createUserLoader({
tenantId: auth.tenantId,
repository: repositories.users
})
};
}Uma instância global pode vazar cache entre usuários.
Mutations
type Mutation {
updateUser(id: ID!, input: UpdateUserInput!): User!
}Valide input, autorize e execute a transação na camada de serviço.
Input types
Não reutilize tipos de output como input. Defina campos permitidos e evite mass assignment.
Validação
GraphQL valida tipos básicos, mas regras como tamanho, formato e combinação de campos precisam de validação adicional.
Custom scalars
Use scalars para DateTime, URL e outros tipos quando houver implementação bem testada. Não aceite qualquer string sem limite.
Erros
GraphQL pode retornar HTTP 200 com campo errors. Padronize códigos:
{
"errors": [{
"message": "Acesso negado",
"extensions": {
"code": "FORBIDDEN"
}
}]
}Não exponha stack
Em produção, remova detalhes internos, SQL, hosts e stack. Preserve request ID para suporte.
formatError
Use para sanitização cuidadosa, sem esconder erros úteis de validação.
Null propagation
Se um campo non-null falha, o erro pode propagar e tornar o objeto pai null. Modele nullability conforme o domínio.
Partial data
GraphQL permite dados parciais com erros. Documente quais campos podem falhar independentemente.
Queries complexas
Um cliente pode criar query profunda e cara. Limite:
- profundidade;
- quantidade de aliases;
- complexidade;
- tamanho do body;
- batching HTTP;
- tempo de execução.
Depth limit
Analise o documento validado e rejeite operações acima do limite. Profundidade sozinha não mede custo.
Complexidade
Atribua custo maior a listas e campos caros. Considere argumentos como first.
Paginação
Listas devem ter limite e cursor. Consulte Paginação em APIs Node.js.
Introspection
Introspection é útil para ferramentas. Em APIs privadas, autentique ou restrinja conforme o risco. Desabilitar não substitui segurança.
Landing page
Configure a interface de exploração de acordo com o ambiente. Produção pode exigir autenticação ou ausência de landing page pública.
CSRF
Apollo Server possui mecanismos de prevenção, mas cookies e integrações exigem configuração de CORS, métodos e headers.
CORS
Use allowlist de origens. Consulte CORS em APIs Node.js.
GET para queries
GET permite cache e persisted queries, mas URLs podem aparecer em logs. Não coloque dados sensíveis em argumentos.
Automatic Persisted Queries
O cliente envia hash da operação e, quando necessário, o documento. Isso reduz payload, mas precisa de cache e limites.
Allowlist de operações
Aplicações controladas podem permitir apenas operações conhecidas. Isso reduz queries arbitrárias, mas exige processo de publicação.
Cache
Apollo pode fornecer hints de cache, mas dados privados precisam de políticas adequadas. Não cacheie resposta de um usuário para outro.
Response cache
Inclua identidade e tenant na chave quando a resposta é personalizada. Considere invalidation.
Data sources
Encapsule clientes REST, bancos e serviços. Defina timeout, retry e circuit breaker.
RESTDataSource
Uma fonte REST pode centralizar headers, cache e tratamento de erros. Não encaminhe Authorization externo indiscriminadamente.
Timeouts
Cada downstream precisa de deadline menor que o prazo da operação.
AbortController
Quando o cliente desconecta, propague cancelamento quando a integração permitir.
Subscriptions
Subscriptions exigem transporte, autenticação, reconexão e autorização por evento. Não assuma que o usuário permanece autorizado para sempre.
WebSocket
Consulte WebSocket no Node.js. Valide token na conexão e antes de eventos sensíveis.
Plugins
Plugins observam lifecycle:
- request;
- parsing;
- validation;
- execution;
- response;
- shutdown.
Plugin de logging
const loggingPlugin = {
async requestDidStart(requestContext) {
const startedAt = performance.now();
return {
async willSendResponse(ctx) {
logger.info({
operationName: ctx.request.operationName,
durationMs: performance.now() - startedAt
}, 'Operação GraphQL concluída');
}
};
}
};Não registre variables completas.
Operation name
Exija nomes em produção quando possível. Eles melhoram métricas e allowlists.
Cardinalidade
Não use a query completa como label de métrica. Use operation name controlado e status.
Tracing
Crie span para operação e downstreams. Evite span por cada campo simples.
Métricas
Monitore:
- operações por nome;
- latência;
- erros;
- complexidade rejeitada;
- N+1;
- downstreams;
- cache hit;
- subscriptions ativas.
Health checks
Separe liveness e readiness. A aplicação pode estar viva, mas incapaz de atender por falta de banco ou schema.
Graceful shutdown
Use plugin de drain do HTTP server ou mecanismo da integração. Pare novas requisições e aguarde operações em andamento.
Consulte Graceful Shutdown no Node.js.
Schema modular
Divida typeDefs e resolvers por domínio. Evite um arquivo único com milhares de linhas.
Code generation
Ferramentas podem gerar tipos TypeScript a partir do schema e operações. Isso reduz divergência, mas não substitui validação runtime.
Schema-first
O contrato SDL orienta implementação e clientes. Mudanças breaking precisam de processo.
Code-first
Bibliotecas criam schema a partir de código e decorators. Avalie transparência do schema gerado.
Breaking changes
Remover campo, tornar nullable em non-null ou mudar tipo pode quebrar clientes. Use depreciação e métricas de uso.
Deprecation
oldField: String @deprecated(
reason: "Use newField"
)Não remova até confirmar que clientes migraram.
Federation
Apollo Server pode funcionar como subgraph. Federação exige ownership de campos, composição, contratos e observabilidade do router.
Subgraph
Um subgraph não deve confiar cegamente em headers do gateway. Use conexão autenticada e identidade assinada ou mTLS.
Testes unitários
Teste serviços e resolvers com context explícito.
executeOperation
const response = await server.executeOperation({
query: `query User($id: ID!) {
user(id: $id) { id name }
}`,
variables: { id: '1' }
}, {
contextValue: testContext
});Teste de autorização
Confirme unauthenticated, forbidden, outro tenant e acesso permitido.
Teste de N+1
Conte chamadas ao repository para lista com relações. O número deve ser constante ou por batch.
Teste de complexidade
Envie query profunda, aliases repetidos e lista com limite alto. A operação deve ser rejeitada antes de executar.
Teste de erro
Simule SQL exception e confirme que stack não aparece na resposta.
Teste de shutdown
Inicie operação lenta, envie SIGTERM e confirme drain antes do encerramento.
Teste de contrato
Valide mudanças de schema no CI e bloqueie breaking changes não aprovadas.
Erros comuns
- Loader global: dados vazam entre usuários.
- Sem limite de query: cliente consome recursos.
- Autorização apenas na UI: campos ficam expostos.
- Resolver com lógica extensa: testes ficam difíceis.
- Variables em logs: dados sensíveis aparecem.
- Sem paginação: listas enormes são resolvidas.
- Shutdown abrupto: operações são interrompidas.
Boas práticas
- Use context por request.
- Autorize no resolver e serviço.
- Use DataLoader.
- Limite profundidade e custo.
- Paginação obrigatória.
- Sanitize erros.
- Nomeie operações.
- Monitore por operação.
- Teste o schema.
- Faça graceful shutdown.
Conclusão
Usar Apollo Server no Node.js fornece uma base completa para APIs GraphQL, com integrações, plugins, context e ferramentas de produção.
A segurança e a performance dependem do desenho ao redor: autorização, DataLoader, limites, paginação, erros e observabilidade. Com schema governado e testes de complexidade, Apollo Server entrega flexibilidade sem permitir que qualquer query se torne uma operação cara ou acesse dados fora do escopo.




