DEV Community

Cover image for GraphQL: Diseñando tu primer esquema
Ramses Mata
Ramses Mata

Posted on

GraphQL: Diseñando tu primer esquema

GraphQL ha sido una tecnología que ha revolucionado el mundo de las APIs y muchas empresas están usando esta tecnología. Si no sabes qué es GraphQL, en mi artículo anterior hablé de esta tecnología, qué es, qué pretende resolver e introduje un pequeño ejemplo que vamos a ver más a detalle en esta serie.

En esta serie de seis artículos, vamos a construir una API de GraphQL completa desde cero. Dividiremos el proceso en las siguientes partes:

  1. GraphQL: Una introducción para nuevos desarrolladores - Qué es GraphQL y qué problemas resuelve
  2. Diseño del esquema (este artículo) - Diseñaremos el contrato de nuestra API
  3. Creando y desplegando tu API - Desplegaremos el schema en AWS AppSync
  4. Implementando resolvers con TypeScript - Construiremos la lógica de queries y mutations
  5. Real-time en aplicaciones web: Polling vs Subscriptions - Entenderemos las opciones para datos en tiempo real
  6. Implementando Subscriptions con AppSync - Agregaremos actualizaciones en vivo a nuestra API

Hoy nos enfocaremos en la parte más fundamental: el diseño del esquema. Como aprendiste en el artículo anterior, el esquema es como un contrato entre el cliente y el servidor que define qué datos están disponibles y cómo están estructurados. Un buen diseño de esquema es crucial para crear una API que sea fácil de usar, mantener y escalar.

Trabajando hacia atrás desde los requisitos del negocio

Antes de escribir una sola línea de código, necesitamos entender el problema que estamos tratando de resolver. Podemos pensar nuestro esquema de GraphQL como un lenguaje compartido entre los miembros de tu equipo, para construir un buen esquema, piensa en el lenguaje que usas para describir tu negocio.

Sigamos con el ejemplo del artículo anterior, en el cual estábamos construyendo una red social. Si fuésemos un usuario de una red social como las que usamos a diario probablemente definiríamos nuestra app de la siguiente manera:

User Stories

Nuestra aplicación necesita por lo menos:

  • Perfiles de usuario con información básica
  • Publicaciones que los usuarios pueden crear
  • Comentarios en las publicaciones

Basándonos en esto podemos deducir algunas entidades:

  • Usuario
  • Publicación
  • Comentario

Y también las relaciones que pueden tener entre ellos.

  • Un Usuario puede crear muchas Publicaciones
  • Un Usuario puede crear muchos Comentarios
  • Una Publicación puede tener muchos Comentarios

Si eres más visual, este diagrama te puede ayudar a entender mejor cómo se relacionan estas entidades:

Entity Relation

Ahora que entendemos nuestro negocio, las entidades involucradas y cómo se relacionan entre ellas, podemos comenzar a pensar en cómo representarlas en GraphQL.

Diseño del esquema y mejores prácticas

Cada entidad principal será un Type, las acciones (crear una publicación, comentar, dar me gusta) serán Mutations, las consultas (obtener el perfil, feed de publicaciones) serán Queries y las actualizaciones en tiempo real serán Subscriptions.

Antes de sumergirnos en la implementación, vamos a revisar algunas buenas prácticas que nos ayudarán a crear esquemas robustos y mantenibles:

  • Usa PascalCase para tipos, interfaces y uniones (ejemplo: Usuario, Publicacion, Comentario)
  • Usa camelCase para campos y argumentos (ejemplo: fotoPerfil, creadoEn)
  • Marca campos como no nulos con ! solo cuando realmente lo sean
  • Usa tipos escalares apropiados (Int, Float, String, Boolean, ID)

Ahora definamos nuestro esquema GraphQL. Pondremos todo en un solo archivo, lo cual funciona bien para proyectos de este tamaño. Conforme un esquema crece, es común dividirlo en varios archivos para mejor organización — eso lo veremos en futuros blog posts.

Construyendo nuestro esquema paso a paso

Antes de empezar, una nota práctica: los esquemas de GraphQL se escriben en archivos con extensión .graphql usando algo llamado SDL (Schema Definition Language). Es un lenguaje específico para definir esquemas, similar a cómo JSON tiene su propia sintaxis.

Para este proyecto, pondremos todo en un solo archivo llamado schema.graphql. Conforme tu esquema crezca, podrás dividirlo en múltiples archivos organizados por dominio o funcionalidad — pero para aprender, un solo archivo es perfecto.

Ahora sí, empecemos con nuestro primer tipo.

Paso 1: Definiendo el tipo Usuario

Empecemos con la entidad más básica: el usuario. Recordemos que en nuestros requisitos dijimos que un usuario necesita tener un perfil con nombre, foto y biografía.

type Usuario {
  id: ID!
  nombre: String!
  email: String!
  fotoPerfil: String
  creadoEn: String!
}
Enter fullscreen mode Exit fullscreen mode

💡 Tip importante: Usa ! solo cuando estés 100% seguro de que el campo siempre tendrá un valor. Si tienes dudas, déjalo opcional. Es más fácil hacer un campo obligatorio después que lidiar con errores porque marcaste algo como obligatorio cuando no debía serlo.

Pero espera... ¿no dijimos que un usuario puede tener muchas publicaciones? Agreguemos esa relación:

type Usuario {
  id: ID!
  nombre: String!
  email: String!
  fotoPerfil: String
  publicaciones: [Publicacion!]!
  creadoEn: String!
}
Enter fullscreen mode Exit fullscreen mode

Aquí pasa algo interesante con publicaciones: [Publicacion!]!:

  • publicaciones es un campo que retorna una lista de publicaciones
  • [Publicacion!]! significa: "una lista que nunca es nula, que contiene Publicaciones que nunca son nulas"

Paso 2: Definiendo el tipo Publicacion

Ahora que tenemos usuarios, necesitamos que puedan crear publicaciones. Una publicación necesita contenido, saber quién la escribió, y poder tener comentarios.

type Publicacion {
  id: ID!
  contenido: String!
  autor: Usuario!
  comentarios: [Comentario!]!
  creadoEn: String!
}
Enter fullscreen mode Exit fullscreen mode

¿Recuerdas el problema N+1 del artículo anterior?

Con REST teníamos que hacer:

  1. GET /api/usuarios/123 - Obtener el usuario
  2. GET /api/usuarios/123/publicaciones - Obtener sus publicaciones
  3. GET /api/publicaciones/456/comentarios - Obtener comentarios de cada publicación
  4. GET /api/usuarios/789 - Obtener info del autor de cada comentario

Con GraphQL, gracias a que autor retorna un Usuario completo y comentarios retorna [Comentario!]!, podemos hacer todo en una sola query. Ya veremos cómo en un momento.

Paso 3: Definiendo el tipo Comentario

Los comentarios son la última pieza de nuestras entidades principales. Un comentario necesita saber qué dice, quién lo escribió, y a qué publicación pertenece.

type Comentario {
  id: ID!
  contenido: String!
  autor: Usuario!
  publicacion: Publicacion!
  creadoEn: String!
}
Enter fullscreen mode Exit fullscreen mode

Navegación bidireccional

Fíjate en algo interesante:

  • Publicacion tiene un campo comentarios que retorna [Comentario!]!
  • Comentario tiene un campo publicacion que retorna Publicacion!

Esto significa que puedes navegar en ambas direcciones. Si tienes una publicación, puedes obtener sus comentarios. Si tienes un comentario, puedes obtener su publicación. Esta flexibilidad es uno de los superpoderes de GraphQL.

Paso 4: Input Types - Preparando para las Mutations

Ahora que tenemos nuestras entidades, necesitamos definir cómo se crearán. Para esto, GraphQL usa algo llamado Input Types. Son como moldes que definen qué información necesitas enviar cuando quieres crear o modificar algo.

input CrearUsuarioInput {
  nombre: String!
  email: String!
  fotoPerfil: String
}

input CrearPublicacionInput {
  contenido: String!
  autorId: ID!
}

input CrearComentarioInput {
  contenido: String!
  publicacionId: ID!
  autorId: ID!
}
Enter fullscreen mode Exit fullscreen mode

¿Por qué necesitamos Input Types separados?

Fíjate que CrearUsuarioInput es diferente a Usuario. ¿Por qué no usar el mismo tipo?

  • Cuando creas un usuario, no tienes id ni creadoEn (el servidor los genera)
  • Cuando consultas un usuario, sí necesitas esos campos
  • Los Input Types solo incluyen lo que el cliente debe enviar

💡 Nota importante: En el próximo artículo veremos cómo usar estos Input Types en Mutations para crear usuarios, publicaciones y comentarios. Por ahora, solo estamos definiendo la estructura.

Paso 5: Definiendo las Queries - ¿Qué podemos consultar?

Las Queries son las operaciones de lectura de tu API. Son como las preguntas que puedes hacerle a tu sistema: "¿Quién es este usuario?" o "¿Cuáles son las últimas publicaciones?"

type Query {
  usuario(id: ID!): Usuario
  publicacion(id: ID!): Publicacion
  publicaciones: [Publicacion!]!
}
Enter fullscreen mode Exit fullscreen mode

¿Qué estamos definiendo?

  • usuario(id: ID!): Usuario - Buscar un usuario específico por su ID. Puede retornar null si no existe.
  • publicacion(id: ID!): Publicacion - Buscar una publicación específica por su ID.
  • publicaciones: [Publicacion!]! - Obtener todas las publicaciones. Siempre retorna una lista (aunque esté vacía).

¿Por qué usuario puede ser null pero publicaciones no?

Cuando buscas un usuario por ID, puede que no exista, entonces retornas null. Pero cuando pides todas las publicaciones, siempre puedes retornar una lista (aunque esté vacía []). Es más predecible para quien usa tu API.

Paso 6: Definiendo las Mutations - ¿Qué podemos modificar?

Las Mutations son las operaciones de escritura de tu API. Son las acciones que cambian el estado de tu sistema: crear usuarios, publicar contenido, comentar.

type Mutation {
  crearUsuario(input: CrearUsuarioInput!): Usuario!
  crearPublicacion(input: CrearPublicacionInput!): Publicacion!
  crearComentario(input: CrearComentarioInput!): Comentario!
}
Enter fullscreen mode Exit fullscreen mode

¿Por qué las Mutations retornan el objeto creado?

Esto es una convención muy útil en GraphQL. Cuando creas algo, inmediatamente obtienes el objeto completo de vuelta, incluyendo campos que el servidor generó (como id y creadoEn). Así el cliente no necesita hacer una query adicional para obtener esa información.

¿Por qué usamos input en lugar de parámetros sueltos?

Compara estas dos opciones:

# ❌ Parámetros sueltos (difícil de mantener)
crearUsuario(nombre: String!, email: String!, fotoPerfil: String): Usuario!

# ✅ Input Type (fácil de extender)
crearUsuario(input: CrearUsuarioInput!): Usuario!
Enter fullscreen mode Exit fullscreen mode

Si mañana necesitas agregar un campo nuevo (como biografia), con Input Types solo modificas CrearUsuarioInput sin romper la firma de la mutation.

El esquema completo

Ahora que hemos construido cada pieza paso a paso, veamos cómo luce nuestro esquema completo:

type Usuario {
  id: ID!
  nombre: String!
  email: String!
  fotoPerfil: String
  publicaciones: [Publicacion!]!
  creadoEn: String!
}

type Publicacion {
  id: ID!
  contenido: String!
  autor: Usuario!
  comentarios: [Comentario!]!
  creadoEn: String!
}

type Comentario {
  id: ID!
  contenido: String!
  autor: Usuario!
  publicacion: Publicacion!
  creadoEn: String!
}

input CrearUsuarioInput {
  nombre: String!
  email: String!
  fotoPerfil: String
}

input CrearPublicacionInput {
  contenido: String!
  autorId: ID!
}

input CrearComentarioInput {
  contenido: String!
  publicacionId: ID!
  autorId: ID!
}

type Query {
  usuario(id: ID!): Usuario
  publicacion(id: ID!): Publicacion
  publicaciones: [Publicacion!]!
}

type Mutation {
  crearUsuario(input: CrearUsuarioInput!): Usuario!
  crearPublicacion(input: CrearPublicacionInput!): Publicacion!
  crearComentario(input: CrearComentarioInput!): Comentario!
}
Enter fullscreen mode Exit fullscreen mode

Conclusión

Hemos diseñado un esquema completo de GraphQL trabajando hacia atrás desde nuestros requisitos de negocio. Empezamos identificando las entidades principales (Usuario, Publicacion, Comentario), definimos sus relaciones, y estructuramos las operaciones que nuestra API necesita soportar.

Lo más importante que aprendimos:

  • El esquema es un contrato - Define exactamente qué puede hacer tu API antes de escribir una línea de código de implementación
  • Piensa en relaciones, no en IDs - GraphQL brilla cuando retornas objetos completos en lugar de solo identificadores
  • Usa ! con intención - Solo marca campos como obligatorios cuando realmente lo sean
  • Input Types separan entrada de salida - Facilitan la evolución de tu API sin romper clientes existentes

En el próximo artículo de esta serie, vamos a desplegar este esquema en AWS AppSync y lo veremos funcionando con datos de prueba. Podrás hacer queries reales y ver tu API en acción, todo sin escribir una sola línea de código de backend todavía.

Por ahora, tienes un esquema sólido que sirve como documentación viva de lo que tu API puede hacer. Y eso, en sí mismo, ya es un gran paso.

Top comments (0)