Como inicializar y resetear una base de datos prisma



En uno de los proyectos en los que estoy trabajando, necesitaba restablecer e inicializar la base de datos Prisma para poder hacer cambios en el esquema GraphQL.

Después de revisar la documentación de Prisma durante un tiempo para aprender a hacerlo, decidí crear esta guía con todos los pasos necesarios para restablecer e inicializar una base de datos prisma.io.

Por tanto, esto es lo que aprenderás en este artículo, cómo conseguí restablecer e inicializar una base de datos Prisma utilizando datos almacenados en un archivo externo.

El esquema GraphQL

Antes de empezar, este es el esquema GraphQL que necesitarás para seguir la guía:

type PaymentMethod {
  id: ID! @unique
  type: String! @unique
}

type Country {
  id: ID! @unique
  name: String!
  image: String!
  cities: [City!]!
}

type City {
  id: ID! @unique
  name: String!
  image: String!
  country: Country!
  neighborhoods: [Neighborhood!]!
}

type Neighborhood {
  id: ID! @unique
  name: String! @unique
  city: City!
}

El esquema está compuesto por 4 tipos. El primero almacena los diferentes métodos de pago que la aplicación acepta. Los otros tipos del esquema están formados por uno llamado Country que tiene una relación con un tipo City y que a su vez tiene una relación con uno Neighborhood.

Si ya tienes datos en la base de datos y quieres hacer cambios que cambiarían el esquema de forma significativa, aveces es más fácil resetear la base de datos entera antes que intentar borrar entradas una por una.

Reseteando la base de datos prisma

Usando el CLI de Prisma es muy sencillo hacerlo si ejecutas en el terminal:

prisma reset

Si no lo tienes instalado en tu maquina, puedes instalarlo con npm install prisma.

Entonces te preguntará si quieres continuar o cancelar el proceso de reseteo de la base de datos:

? Are you sure that you want to reset the data of server in stage dev? y/N (n)

Si necesitas usar un fichero .env puedes añadir el flag --env-file.

prisma reset --env-file .env

Ahora ya tendrás la base de datos Prisma reseteada con todos los datos anteriores eliminados.

Resetting server@dev 4748ms

Una vez has reseteado la base de datos –o si es la primera vez que usas Prisma– puedes empezar con la inicialización de los datos.

Almacenando los datos

Siguiendo el esquema GraphQL anterior, usaré un fichero seed.js que contiene la lógica para inicializar la base de datos y un fichero seedData.js que contiene los datos necesarios.

Primero, el fichero seedData.js contiene varios objetos con los datos necesarios en las mutaciones para inicializar la base de datos.

//seedData.js
const paymentMethods = ["VISA", "MASTERCARD", "Paypal"];

const spainCities = [
  {
    name: "Barcelona",
    image: "https://res.cloudinary.com/...",
    neighborhoods: {
      create: [
        { name: "El Raval" },
        { name: "Gothic Quarter" },
        { name: "La Barceloneta" },
        { name: "El Poblenou" },
      ],
    },
  },
  {
    name: "Madrid",
    image: "https://res.cloudinary.com/...",
    neighborhoods: {
      create: [
        { name: "Chueca" },
        { name: "Las Cortes" },
        { name: "Huertas" },
        { name: "Gran Vía" },
      ],
    },
  },
];

module.exports = {
  paymentMethods,
  spainCities,
};

El objeto paymentMethods esta formado por un array de strings con los nombres de los métodos de pago.

spainCities es un array de objetos que contienen los nombres de las ciudades y los barios de cada una de ellas.

También puedes escribir todo en el mismo objeto y saltar el paso de importar, pero mi esquema GraphQL es bastante grande y decidí guardar parte de los datos en un fichero externo para poder reducir la complejidad del fichero seedData.js.

Inicializando la base de datos

El siguiente paso es crear un fichero llamado seed.js para poder inicializar la base de datos.

Primero, necesitarás importar el servidor Prisma que tiene la conexión con la base de datos. Despúes, tendrás que importar los objetos que contienen los datos del fichero seedData.js –si has decidido guardarlos en un fichero externo, si no, puedes saltar este paso.

// the prisma server
const db = require("./db");

// the data to seed in the database
const { paymentMethods, spainCities } = require("./seedData");

El siguiente paso es crear una función para poder inicializar la información que quieres guardar en la base de datos Prisma.

async function main() {
  paymentMethods.forEach(async (method) => {
    await db.mutation.createPaymentMethod({
      data: { type: method },
    });
  });

  await db.mutation.createCountry({
    data: {
      name: "Spain",
      image: "https://res.cloudinary.com/...",
      cities: {
        create: spainCities,
      },
    },
  });
}

Como puedes ver, estoy usando dos mutaciones diferentes para crear los métodos de pago y el país. Para ello, utilizo un bucle forEach para poder reducir la cantidad de código que tengo que escribir a mano, ya que el bucle ejecutará cada mutación individualmente.

Si no tienes que crear demasiados datos, también puedes usar una mutación para cada uno de ellos. De esta manera, tendrás una mutación para cada dato que quieres guardar en la base de datos.

async function main() {
  await db.mutation.createPaymentMethod({
    data: { type: "VISA" },
  });
  await db.mutation.createPaymentMethod({
    data: { type: "MASTERCARD" },
  });
  await db.mutation.createPaymentMethod({
    data: { type: "Paypal" },
  });

  await db.mutation.createCountry({
    data: {
      name: "Spain",
      image: "https://res.cloudinary.com/...",
      cities: {
        create: spainCities,
      },
    },
  });
}

Si decides usar el bucle para guardar los datos, recuerda que tendrás que hacer que la función callback del bucle forEach sea del tipo async. De lo contrario, devolverá un error.

Resetting server@dev 4397ms
server/src/seed.js:45
    await db.mutation.createRoomAmenityType({    ^^^^^
SyntaxError: await is only valid in async function    at Module._compile (internal/modules/cjs/loader.js:718:23)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:785:10)
 ...

Ahora para ejecutar la función de inicialización solo tienes que llamarla con:

main().catch((e) => console.error(e));

Si la función devuelve algún error cuando se ejecutan las mutaciones, lo podrás ver en la consola del terminal.

La mutación createCountry no esta dentro de un bucle también ya que solo guardo un país, pero si necesitas crear mas de uno, puedes usar la misma lógica que en el caso de los metodos de pago y hacerlo con un bucle forEach.

Para crear las ciudades, barrios y sus relaciones con el tipo pariente Country, estoy usando el método create de la mutación.

De esta manera, puedo crear varios objetos a la vez del tipo pariente Country sin tener que crearlos uno a uno y después tener que conectarlos al mismo.

await db.mutation.createCountry({
  data: {
    name: "Spain",
    image: "https://res.cloudinary.com/...",
    cities: {
      create: spainCities,    },
  },
});

El método create acepta un array de objetos, los cuales están almacenanados en el objeto spainCities.

Si no los almacenaste en un fichero externo, el código equivalente sería escribirlos en el mismo objeto en el método create.

await db.mutation.createCountry({
  data: {
    name: "Spain",
    image: "https://res.cloudinary.com/...",
    cities: {
      create: [
        {
          name: "Barcelona",
          image: "https://res.cloudinary.com/...",
          neighborhoods: {
            create: [
              { name: "El Raval" },
              { name: "Gothic Quarter" },
              { name: "La Barceloneta" },
              { name: "El Poblenou" },
            ],
          },
        },
        {
          name: "Madrid",
          image: "https://res.cloudinary.com/...",
          neighborhoods: {
            create: [
              { name: "Chueca" },
              { name: "Las Cortes" },
              { name: "Huertas" },
              { name: "Gran Vía" },
            ],
          },
        },
      ],
    },
  },
});

De esta manera puedes crear muchos tipos anidados en el mismo tipo pariente que los contiene.

En este caso, el tipo pariente es el Country, el cual tiene una relación con el tipo City, el cual a su vez tiene una relación con el tipo Neighborhood.

Si ya has creado los tipos previamente y tienes el id de cada uno de ellos, puedes usar el método connect en vez del create en la mutación, para conectarlos con el tipo pariente, ya que tanto el método create como el connect aceptan una lista de objetos.

await db.mutation.createCountry({
  data: {
    name: "Spain",
    image: "https://res.cloudinary.com/...",
    cities: {
      create: [
        {
          name: "Barcelona",
          image: "https://res.cloudinary.com/...",
          neighborhoods: {
            connect: [
              { id: "id1" },
              { id: "id2" },
              { id: "id3" },
              { id: "id4" },
            ],
          },
        },
      ],
    },
  },
});

Si juntas todo en el fichero final seed.js, tendrás lo siguiente:

//seed.js
const db = require("./db");

const { paymentMethods, spainCities } = require("./seedData");

paymentMethods.forEach(async (method) => {
  await db.mutation.createPaymentMethod({
    data: { type: method },
  });
});

await db.mutation.createCountry({
  data: {
    name: "Spain",
    image: "https://res.cloudinary.com/...",
    cities: {
      create: spainCities,
    },
  },
});

main().catch((e) => console.error(e));

Reseteando e inicializando la base de datos

Una vez tienes todo el código necesario, puedes empezar a resetear e inicializar la base de datos.

En tu terminal, escribe prisma seed ./seed.js. Si quieres resetear la base de datos primero y después inicializarla, puedes añadir el flag --reset, para primero hacer el reseteo y después almacenar los datos.

prisma seed --reset ./seed.js

Si necesitas usar un fichero .env, puedes hacerlo de la siguiente manera:

prisma seed --reset --env-file .env ./seed.js

Si todo ha salido bien 🤞, ahora deberías tener la base de datos reseteada e inicializada con tus datos.

Conclusiones

Esto es básicamente todo lo que necesitas para poder resetear e inicializar una base de datos Prisma usando su cli.

Cuando estaba intentando hacerlo por primera vez en mi proyecto, tuve que buscar mucho en la documentación oficial de prisma.io y en varias entradas en StackOverflow antes de entender como funciona todo. Lo más útil que aprendí fue que los métodos create y connect acceptan una lista de objetos para poder crear o conectar varios tipos a la vez en la misma mutación.

create: [
  { name: "Chueca" },
  { name: "Las Cortes" },
  { name: "Huertas" },
  { name: "Gran Vía" },
],

...
connect: [
  { id: "id1" },
  { id: "id2" },
  { id: "id3" },
  { id: "id4" },
],

Espero que esta guía te haya ayudado a aprender como puedes resetear e inicializar tu base de datos Prisma. Si tienes alguna pregunta, no dudes en dejar un comentario.