Skip to content

JSConfCL/gql_api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Como Ejecutar el proyecto

  • Asegurate de tener el archivo .dev.vars (Pídele al equipo los valores correspondientes.)
    • Puedes correr una BDD local si te parece.
    • Para correr el proyecto con las BDD de desarrollo, tienes que agregar un archivo .dev.vars con los valores de las mismas.
  • Crear una base de datos en postgres.
    • Nosotros usamos Neon.tech
    • Guarda el string de conexión a tu BDD en el archivo .dev.vars bajo NEON_URL
  • Finalmente, npm i & num run dev
  • Listo! Tu servidor GraphQL está corriendo en http://127.0.0.1:8787

Cómo ver el esquema de la BDD?

El esquema de la BDD se genera corriendo npm run db:model y se puede visualizar en https://dbdiagram.io

La documentación de nuestro modelo está en https://dbdocs.io/fforres/API?view=relationships (O haz click en la imagen)

Cómo escribir tests

PSA: Los tests se corren con npm run tests (o npm run test:interactive si quieres explorar una UI con mas informacion)

Preparación del entorno de prueba

La expectativa de nuestros tests, es que cada archivo de tests pueda correr en paralelo (y aislados) el uno del otro. Por lo mismo, creamos una base de datos nueva antes de correr cada test. Para asegurarte de que esto tambien ocurra entre tests del mismo archivo, recuerda poner en tu archivo de tests, un hook afterEach que limpie la base de datos. (En nuestros ejemplos, usamos la función clearDatabase() en el hook afterEach() para lograr esto.)

Escribir la query o mutación

(Ejemplos en esta carpeta) Define la query o mutation GraphQL que quieres que tu resolver maneje en un archivo .gql. Por ejemplo

query getUsers {
  users {
    id
    name
    email
  }
}

Generar código

Para poder correr los test, necesitamos tener funciones y variables en JavaScript/Typescript, no en "graphql". Podríamos escribir la query o mutación, como un string, directamente en un archivo .ts. Pero eso es tedioso, propenso a errores, y no nos da autocompletado.

Para evitar esto, usamos un generador de código, que nos permite escribir la query o mutación en un archivo .gql, y luego generar el código asociado a esta operación de graphql.

Usaremos el archivo .gql que creamos recien, y correremos el comando npm run generate.

Esto generará codigo y tipos asociados a esta operación de graphql, por ejemplo, la query que definimos arriba, generará el siguiente archivo:

/* eslint-disable */
/* @ts-nocheck */
/* prettier-ignore */
/* This file is automatically generated using `npm run graphql:types` */
import type * as Types from '../../generated/types';

import type { JsonObject } from "type-fest";
import gql from "graphql-tag";
export type GetUsersQueryVariables = Types.Exact<{ [key: string]: never }>;

export type GetUsersQuery = {
  __typename?: "Query";
  users: Array<{
    __typename?: "User";
    id: string;
    name: string | null;
    email: string | null;
  }>;
};

export const GetUsers = gql`
  query getUsers {
    users {
      id
      name
      email
    }
  }
`;

Escribir el test

En nuestro caso, usamos vitest. Tu test debe ejecutar la consulta o mutación usando, y luego verificar que la respuesta sea la esperada. Aquí hay un ejemplo:

describe("My Resolver Tests", () => {
  it("Should return the correct data", async () => {
    // Acá irá el codigo del test
  });
});

Inserta data en tu BDD

Usa (o crea) métodos en ./src/tests/__fixtures/index.ts para pre-popular tu BDD.

Por ejemplo:

import { insertUser } from "~/tests/__fixtures";
describe("My Resolver Tests", () => {
  it("Should return the correct data", async () => {
    const user = await insertUser();
    const user2 = await insertUser({ email: "[email protected]" });
  });
});

Ejecuta tu query o mutación.

Tenemos un helper para realizar esto, llamado executeGraphqlOperation. Donde puedes pasar la operación que definiste en el parametro "document". La query y tipos los debes traer desde tu archivo generado.

Por ejemplo:

🚨🚨 IMPORTANTE 🚨🚨 executeGraphqlOperation puede recibir genéricos de typescript para hacer más facil escribir tests, recuerda pasarlos.

import { insertUser, executeGraphqlOperation } from "~/tests/__fixtures";
import {
  GetUsers,
  GetUsersQuery,
  GetUsersQueryVariables,
} from "~/tests/user/getUsers.generated";

describe("My Resolver Tests", () => {
  it("Should return the correct data", async () => {
    const user = await insertUser();
    const user2 = await insertUser({ email: "[email protected]" });
    const response = await executeGraphqlOperation<
      GetUsersQuery,
      GetUsersQueryVariables,
    >({
      document: GetUsers,
    });
  });
});

Ejecuta tu query o mutación con otro usuario.

executeGraphqlOperation executa la query o mutación de manera anónima. Para poder executar una query o mutación con un usuario específico, puedes usar el helper executeGraphqlOperationAsUser. Este helper requiere que ademas de un documento/variables, le pases un usuario. como segundo parámetro.

Por ejemplo:

const user1 = await insertUser();
const response = await executeGraphqlOperationAsUser<
  CreateEventMutation,
  CreateEventMutationVariables
>(
  {
    document: SOME_DOCUMENT,
    variables: SOME_VARIABLES,
  },
  user1,
);

De igual manera, tenemos el helper executeGraphqlOperationAsAdmin para ejecutar queries o mutaciones como un admin. Es un helper similar a executeGraphqlOperationAsUser, con la salvedad que no requiere que le pases un usuario como segundo parámetro, y lo crea por ti.

Verifica tus respuestas:

Puedes usar métodos assert o expect de Vitest

import { insertUser. executeGraphqlOperation } from "~/tests/__fixtures";
import {
  GetUsers,
  GetUsersQuery,
  GetUsersQueryVariables,
} from "~/tests/user/getUsers.generated";

describe("My Resolver Tests", () => {
  it("Should return the correct data", async () => {
    const user = await insertUser();
    const user2 = await insertUser({ email: "[email protected]" });
    const response = await executeGraphqlOperation<
      GetUsersQuery,
      GetUsersQueryVariables,
    >({
      document: GetUsers,
    });
    assert.equal(response.errors, undefined);
    assert.equal(response.data?.users.length, 2);
    assert.equal(response.data?.users[0].id, user.id);
    assert.equal(response.data?.users[1].id, user2.id);
  });
});

Manejo de errores

Además de verificar que tu resolver devuelve los datos correctos, verifica cómo maneja los errores. Por ejemplo, puedes escribir un test que pase datos incorrectos a tu mutación y luego verificar que la respuesta contiene el error correcto, o que falla donde debería fallar 😀.

Migraciones

Nuestra BDD es postgres, en produccion/qa y development, usando libsql en local. Usamos drizzle y drizzle-kit para manejar conexiones a la BDD, que genera automaticamente archivos de migraciones cuando cambias tus modelos, lo que hace muchisimo más facil es escribrlas.

Cómo escribir migraciones?

1. Actualiza el esquema de la base de datos.

AKA. Crea o edita Tablas, Columnas, Indices, etc.

Primero, necesitas actualizar el archivo del esquema en ./src/datasources/db/schema/tables.ts. (o ) Este archivo define la estructura de las tablas en la BDD.

Las relaciones entre tablas, se definen en ./src/datasources/db/schema/relations.ts. Los esquemas de query/update se definen en ./src/datasources/db/schema/CRUD.ts.

Para definir una tabla, utilizas la función pgTable(), donde el primer argumento es el nombre de la tabla y el segundo es un objeto que define los campos de la tabla.

Por ejemplo:

export const usersSchema = pgTable("users", {
  id: text("id").unique().notNull(),
  name: text("name"),
  bio: text("bio", { length: 1024 }),
  // ...
});

Esto define una tabla users con varios campos, incluyendo id, name, y bio. Los campos se definen utilizando funciones como text() e integer(), y se pueden agregar opciones adicionales, como unique(), notNull(), y default().

2. Genera los archivos de migración.

Una vez que hayas actualizado el esquema, debes generar los archivos de migración. Para hacerlo, ejecuta npx db:generate.

3. Verifica las migraciones:

Antes de ejecutar las migraciones, verifica que están funcionando correctamente. Puedes hacer esto corriendo todos los tests. Estos geeneran una BDD desde 0, y corren todas las migraciones antes de correr los tests.

4. Ejecuta las migraciones:

Finalmente, ejecuta las migraciones con npm run db:migrate. Estos comandos utilizan las variables de entorno definidas en el archivo .dev.vars para conectarse a las BDD de desarrollo.

Requisitos

  • Tener un archivo .dev.vars con el siguiente contenido
    NEON_URL="PREGUNTALE AL EQUIPO POR ESTO"
    HIGHLIGHT_PROJECT_ID="PREGUNTALE AL EQUIPO POR ESTO"
    RESEND_API_KEY="PREGUNTALE AL EQUIPO POR ESTO"
    SANITY_PROJECT_ID="PREGUNTALE AL EQUIPO POR ESTO"
    SANITY_DATASET="PREGUNTALE AL EQUIPO POR ESTO"
    SANITY_API_VERSION="PREGUNTALE AL EQUIPO POR ESTO"
    SANITY_SECRET_TOKEN="PREGUNTALE AL EQUIPO POR ESTO"
    STRIPE_KEY="PREGUNTALE AL EQUIPO POR ESTO"
    SUPABASE_JWT_DECODER"PREGUNTALE AL EQUIPO POR ESTO"

BRO-TIP 🔥 Agrega una variable ENFORCED_JWT_TOKEN a tu archivo .dev.vars, para utilizarla por defecto en graphiql.

STACK

  • Neon Tech: Servicio de Postgres Serverless, en el futuro, manejando el pool de conexiones mediante cloudflare hyperdrive, lo que permite mejorar la latencia de las consultas a la base de datos a nivel global.

  • GraphQL Yoga: Un servidor GraphQL fácil de configurar que se apoya en Express.js. Proporciona una forma sencilla de crear servidores GraphQL que se pueden conectar a cualquier fuente de datos.

  • Zod: Una biblioteca de validación y análisis de datos para JavaScript y TypeScript. Se utiliza para definir y validar esquemas de datos.

  • Drizzle: Un query builder para TypeScript. Ayuda a interactuar con nuestra bases de datos de una manera más estructurada y segura. Además de ayudarnos a crear migraciones incrementales facilmente.

  • Cloudflare Workers: Una plataforma de servidor ...sin servidor 😊 Que permite ejecutar código en la red de distribución de contenido (CDN) de Cloudflare, lo que permite respuestas más rápidas y menor latencia al ejecutar código cerca del usuario final​1.