Construye una API GraphQL con Apollo Server y TypeScript
Como construir una API GraphQL lista para produccion usando Apollo Server, TypeScript y DataLoader para resolver el problema de consultas N+1
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Construye una API GraphQL con Apollo Server y TypeScript
GraphQL permite a los clientes solicitar exactamente los datos que necesitan en una sola consulta. Apollo Server proporciona un framework listo para produccion para construir APIs GraphQL con desarrollo schema-first, soporte integrado de suscripciones y un rico ecosistema de plugins.
Cuando Usar Esto
- Los clientes necesitan consultas flexibles sobre un modelo de dominio complejo
- Quieres reducir over-fetching y under-fetching comunes en APIs REST
- Las actualizaciones en tiempo real via suscripciones son un requerimiento
Requisitos Previos
- Node.js 18+
- Comprension basica de sintaxis de schemas GraphQL
Solucion
1. Instalar Dependencias
npm install @apollo/server graphql graphql-tag
npm install -D @types/node typescript
2. Definir el Schema
// schema.ts
import gql from 'graphql-tag';
export const typeDefs = gql`
type User {
id: ID!
email: String!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users(limit: Int = 10): [User!]!
posts: [Post!]!
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
`;
3. Implementar Resolvers con DataLoader
// resolvers.ts
import DataLoader from 'dataloader';
// Carga batch de usuarios por ID para resolver N+1
const createUserLoader = (db: DbConnection) =>
new DataLoader(async (userIds: readonly string[]) => {
const users = await db.users.findMany({ where: { id: { in: [...userIds] } } });
return userIds.map(id => users.find(u => u.id === id));
});
export const createResolvers = (db: DbConnection) => {
const userLoader = createUserLoader(db);
return {
Query: {
user: (_: unknown, { id }: { id: string }) => db.users.findById(id),
users: (_: unknown, { limit }: { limit: number }) =>
db.users.findMany({ take: limit }),
posts: () => db.posts.findMany(),
},
Mutation: {
createPost: (_: unknown, args: { title: string; content: string; authorId: string }) =>
db.posts.create(args),
},
Post: {
author: (post: Post) => userLoader.load(post.authorId),
},
User: {
posts: (user: User) => db.posts.findMany({ where: { authorId: user.id } }),
},
};
};
4. Crear el Servidor
// server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema';
import { createResolvers } from './resolvers';
import { db } from './db';
const server = new ApolloServer({
typeDefs,
resolvers: createResolvers(db),
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
const user = token ? await verifyToken(token) : null;
return { user, db };
},
});
console.log(`Servidor listo en ${url}`);
5. Middleware de Autenticacion
// auth.ts
export const authDirective = (schema: GraphQLSchema) =>
mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const authDirective = getDirective(schema, fieldConfig, 'auth')?.[0];
if (authDirective) {
const { resolve = defaultFieldResolver } = fieldConfig;
fieldConfig.resolve = (source, args, context, info) => {
if (!context.user) throw new Error('No autorizado');
return resolve(source, args, context, info);
};
}
return fieldConfig;
},
});
Como Funciona
- Definicion de Schema sirve como contrato entre cliente y servidor
- Resolvers obtienen datos para cada campo, componibles y testeables independientemente
- DataLoader agrupa peticiones en un solo tick del event loop
- Context transporta autenticacion y conexiones a base de datos por peticion
Consideraciones de Produccion
- Usa Apollo Federation para componer multiples servicios GraphQL en un gateway unificado
- Habilita cacheo de respuestas con directivas
@cacheControlpara consultas GET - Implementa rate limiting por complejidad de operacion, no solo conteo de peticiones
- Agrega safelisting de operaciones para prevenir consultas arbitrarias costosas en produccion
FAQ
P: Debo usar Apollo Server o GraphQL Yoga? R: Apollo Server tiene el ecosistema mas grande. Yoga es mas ligero y rapido para casos simples. Ambos son aptos para produccion.
P: Como manejo uploads de archivos en GraphQL?
R: Usa graphql-upload-minimal con peticiones multipart, o prefiere un endpoint REST separado para archivos grandes.
P: Cuando deberia evitar GraphQL? R: Para CRUD simple con pocas relaciones, REST es usualmente mas simple. GraphQL brilla cuando los clientes necesitan consultas flexibles sobre grafos complejos.