diff --git a/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx b/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx index 0da5ee1123..cc8f89cce2 100644 --- a/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx +++ b/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx @@ -182,3 +182,201 @@ Medusa will run any pending migrations and migration scripts, including your scr If the script runs successfully, Medusa won't run the script again. If there are errors in the script, you'll receive an error in the migration script logs. Medusa will keep running the script every time you run the migration command until it runs successfully. + +--- + +## Migration Examples + +The following section provides examples of writing migrations for common database changes. + +### Example: Migration with Relationship + +Consider you have a module with two data models: `Author` and `Post`. An author can have multiple posts, so there's a one-to-many relationship between the two models: + +```ts +import { model } from "@medusajs/framework/utils" + +const Post = model.define("post", { + id: model.id().primaryKey(), + title: model.text(), + author: model.belongsTo(() => Author, { + mappedBy: "posts" + }) +}) + +const Author = model.define("author", { + id: model.id().primaryKey(), + name: model.text(), + posts: model.hasMany(() => Post, { + mappedBy: "author" + }) +}) +``` + +To create a migration that reflects this relationship in the database, you can create a migration file as follows: + +```ts title="src/modules/blog/migrations/Migration202507021200_create_author_and_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230112505 extends Migration { + + override async up(): Promise { + this.addSql(`create table if not exists "author" ("id" text not null, "name" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "author_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_author_deleted_at" ON "author" ("deleted_at") WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "post" ("id" text not null, "title" text not null, "author_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_author_id" ON "post" ("author_id") WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_deleted_at" ON "post" ("deleted_at") WHERE deleted_at IS NULL;`); + + this.addSql(`alter table if exists "post" add constraint "post_author_id_foreign" foreign key ("author_id") references "author" ("id") on update cascade;`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" drop constraint if exists "post_author_id_foreign";`); + + this.addSql(`drop table if exists "author" cascade;`); + + this.addSql(`drop table if exists "post" cascade;`); + } + +} +``` + +In this migration, the `up` method creates the `author` and `post` tables, including the foreign key constraint that establishes the relationship between them. The `down` method removes the foreign key constraint and drops both tables. + +### Example: Migration to Add a New Column + +Consider you have an existing `Post` data model, and you added a new `published_at` column to it. + +You can create a migration file to add this new column as follows: + +```ts title="src/modules/blog/migrations/Migration202507021300_add_published_at_to_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113025 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" drop column if exists "published_at";`); + } + +} +``` + +In this migration, the `up` method adds the `published_at` column to the `post` table, while the `down` method removes it. + +### Example: Migration to Remove a Column + +Consider you have an existing `Post` data model, and you removed the `published_at` column from it. + +You can create a migration file to remove this column as follows: + +```ts title="src/modules/blog/migrations/Migration202507021400_remove_published_at_from_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113125 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "post" drop column if exists "published_at";`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`); + } + +} +``` + +In this migration, the `up` method removes the `published_at` column from the `post` table, while the `down` method adds it back. + +### Example: Migration to Rename a Column + +Consider you have an existing `Post` data model with a `title` column, and you renamed it to `headline`. + +You can create a migration file to rename this column as follows: + +```ts title="src/modules/blog/migrations/Migration202507021500_rename_title_to_headline_in_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113214 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "post" rename column "title" to "headline";`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" rename column "headline" to "title";`); + } + +} +``` + +In this migration, the `up` method renames the `title` column to `headline` in the `post` table, while the `down` method renames it back to `title`. + +### Example: Migration to Create an Index + +Consider you have an existing `Post` data model, and add an index on the `headline` column to improve query performance: + +export const indexHighlights = [ + ["6", "index", "Define the index on the 'headline' column"], +] + +```ts title="src/modules/blog/models/post.ts" highlights={indexHighlights} +import { model } from "@medusajs/framework/utils" +import { Author } from "./author" + +export const Post = model.define("post", { + id: model.id().primaryKey(), + headline: model.text().index(), + author: model.belongsTo(() => Author, { + mappedBy: "posts" + }) +}) +``` + +You can create a migration file to create this index as follows: + +```ts title="src/modules/blog/migrations/Migration202507021600_create_index_on_headline_in_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113322 extends Migration { + + override async up(): Promise { + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`); + } + + override async down(): Promise { + this.addSql(`drop index if exists "IDX_post_headline";`); + } + +} +``` + +In this migration, the `up` method creates an index on the `headline` column of the `post` table, while the `down` method removes the index. + +### Example: Migration to Drop an Index + +Consider you have an existing `Post` data model with an index on the `headline` column, and you decide to remove this index. + +You can create a migration file to drop this index as follows: + +```ts title="src/modules/blog/migrations/Migration202507021700_drop_index_on_headline_in_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113350 extends Migration { + + override async up(): Promise { + this.addSql(`drop index if exists "IDX_post_headline";`); + } + + override async down(): Promise { + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`); + } + +} +``` + +In this migration, the `up` method drops the index on the `headline` column of the `post` table, while the `down` method recreates the index. diff --git a/www/apps/book/app/learn/introduction/architecture/page.mdx b/www/apps/book/app/learn/introduction/architecture/page.mdx index 7102cc5a3f..2baefd33ec 100644 --- a/www/apps/book/app/learn/introduction/architecture/page.mdx +++ b/www/apps/book/app/learn/introduction/architecture/page.mdx @@ -37,6 +37,8 @@ These layers of stack can be implemented within [plugins](../../fundamentals/plu The Medusa application injects into each module, including your [custom modules](../../fundamentals/modules/page.mdx), a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database. +Medusa only supports PostgreSQL as the underlying database. You can also integrate other databases in a [custom module](../../fundamentals/modules/page.mdx). For example, the [Loader chapter](../../fundamentals/modules/loaders/page.mdx#example-register-custom-mongodb-connection) shows how to connect to a MongoDB database through a custom module. + Modules can be implemented within [plugins](../../fundamentals/plugins/page.mdx). diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index 428e49f1ae..4a3d9ae775 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -32,7 +32,7 @@ export const generatedEditDates = { "app/learn/fundamentals/admin/page.mdx": "2025-11-26T10:47:12.412Z", "app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-08-01T07:16:21.736Z", "app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-08-01T13:11:18.823Z", - "app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-10-28T16:01:36.609Z", + "app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-12-30T11:36:13.929Z", "app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2025-04-25T14:16:41.124Z", "app/learn/fundamentals/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00", "app/learn/fundamentals/modules/options/page.mdx": "2025-03-18T15:12:34.510Z", @@ -89,7 +89,7 @@ export const generatedEditDates = { "app/learn/customization/integrate-systems/service/page.mdx": "2024-12-09T11:02:39.594Z", "app/learn/customization/next-steps/page.mdx": "2025-04-17T08:50:17.036Z", "app/learn/fundamentals/modules/infrastructure-modules/page.mdx": "2025-05-21T14:31:51.644Z", - "app/learn/introduction/architecture/page.mdx": "2025-07-18T15:52:35.513Z", + "app/learn/introduction/architecture/page.mdx": "2025-12-30T11:12:26.485Z", "app/learn/fundamentals/data-models/infer-type/page.mdx": "2025-03-18T07:41:01.936Z", "app/learn/fundamentals/custom-cli-scripts/seed-data/page.mdx": "2025-09-15T16:02:51.362Z", "app/learn/fundamentals/environment-variables/page.mdx": "2025-11-26T11:05:38.863Z", diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index 1f5117b0e0..59045bef3a 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -13966,6 +13966,200 @@ If the script runs successfully, Medusa won't run the script again. If there are errors in the script, you'll receive an error in the migration script logs. Medusa will keep running the script every time you run the migration command until it runs successfully. +*** + +## Migration Examples + +The following section provides examples of writing migrations for common database changes. + +### Example: Migration with Relationship + +Consider you have a module with two data models: `Author` and `Post`. An author can have multiple posts, so there's a one-to-many relationship between the two models: + +```ts +import { model } from "@medusajs/framework/utils" + +const Post = model.define("post", { + id: model.id().primaryKey(), + title: model.text(), + author: model.belongsTo(() => Author, { + mappedBy: "posts" + }) +}) + +const Author = model.define("author", { + id: model.id().primaryKey(), + name: model.text(), + posts: model.hasMany(() => Post, { + mappedBy: "author" + }) +}) +``` + +To create a migration that reflects this relationship in the database, you can create a migration file as follows: + +```ts title="src/modules/blog/migrations/Migration202507021200_create_author_and_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230112505 extends Migration { + + override async up(): Promise { + this.addSql(`create table if not exists "author" ("id" text not null, "name" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "author_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_author_deleted_at" ON "author" ("deleted_at") WHERE deleted_at IS NULL;`); + + this.addSql(`create table if not exists "post" ("id" text not null, "title" text not null, "author_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_pkey" primary key ("id"));`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_author_id" ON "post" ("author_id") WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_deleted_at" ON "post" ("deleted_at") WHERE deleted_at IS NULL;`); + + this.addSql(`alter table if exists "post" add constraint "post_author_id_foreign" foreign key ("author_id") references "author" ("id") on update cascade;`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" drop constraint if exists "post_author_id_foreign";`); + + this.addSql(`drop table if exists "author" cascade;`); + + this.addSql(`drop table if exists "post" cascade;`); + } + +} +``` + +In this migration, the `up` method creates the `author` and `post` tables, including the foreign key constraint that establishes the relationship between them. The `down` method removes the foreign key constraint and drops both tables. + +### Example: Migration to Add a New Column + +Consider you have an existing `Post` data model, and you added a new `published_at` column to it. + +You can create a migration file to add this new column as follows: + +```ts title="src/modules/blog/migrations/Migration202507021300_add_published_at_to_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113025 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" drop column if exists "published_at";`); + } + +} +``` + +In this migration, the `up` method adds the `published_at` column to the `post` table, while the `down` method removes it. + +### Example: Migration to Remove a Column + +Consider you have an existing `Post` data model, and you removed the `published_at` column from it. + +You can create a migration file to remove this column as follows: + +```ts title="src/modules/blog/migrations/Migration202507021400_remove_published_at_from_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113125 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "post" drop column if exists "published_at";`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`); + } + +} +``` + +In this migration, the `up` method removes the `published_at` column from the `post` table, while the `down` method adds it back. + +### Example: Migration to Rename a Column + +Consider you have an existing `Post` data model with a `title` column, and you renamed it to `headline`. + +You can create a migration file to rename this column as follows: + +```ts title="src/modules/blog/migrations/Migration202507021500_rename_title_to_headline_in_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113214 extends Migration { + + override async up(): Promise { + this.addSql(`alter table if exists "post" rename column "title" to "headline";`); + } + + override async down(): Promise { + this.addSql(`alter table if exists "post" rename column "headline" to "title";`); + } + +} +``` + +In this migration, the `up` method renames the `title` column to `headline` in the `post` table, while the `down` method renames it back to `title`. + +### Example: Migration to Create an Index + +Consider you have an existing `Post` data model, and add an index on the `headline` column to improve query performance: + +```ts title="src/modules/blog/models/post.ts" highlights={indexHighlights} +import { model } from "@medusajs/framework/utils" +import { Author } from "./author" + +export const Post = model.define("post", { + id: model.id().primaryKey(), + headline: model.text().index(), + author: model.belongsTo(() => Author, { + mappedBy: "posts" + }) +}) +``` + +You can create a migration file to create this index as follows: + +```ts title="src/modules/blog/migrations/Migration202507021600_create_index_on_headline_in_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113322 extends Migration { + + override async up(): Promise { + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`); + } + + override async down(): Promise { + this.addSql(`drop index if exists "IDX_post_headline";`); + } + +} +``` + +In this migration, the `up` method creates an index on the `headline` column of the `post` table, while the `down` method removes the index. + +### Example: Migration to Drop an Index + +Consider you have an existing `Post` data model with an index on the `headline` column, and you decide to remove this index. + +You can create a migration file to drop this index as follows: + +```ts title="src/modules/blog/migrations/Migration202507021700_drop_index_on_headline_in_post.ts" +import { Migration } from "@medusajs/framework/mikro-orm/migrations"; + +export class Migration20251230113350 extends Migration { + + override async up(): Promise { + this.addSql(`drop index if exists "IDX_post_headline";`); + } + + override async down(): Promise { + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`); + } + +} +``` + +In this migration, the `up` method drops the index on the `headline` column of the `post` table, while the `down` method recreates the index. + # Environment Variables @@ -25846,6 +26040,8 @@ These layers of stack can be implemented within [plugins](https://docs.medusajs. The Medusa application injects into each module, including your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database. +Medusa only supports PostgreSQL as the underlying database. You can also integrate other databases in a [custom module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). For example, the [Loader chapter](https://docs.medusajs.com/learn/fundamentals/modules/loaders#example-register-custom-mongodb-connection/index.html.md) shows how to connect to a MongoDB database through a custom module. + Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). ![Database layer architecture diagram showing how Medusa modules establish connections to PostgreSQL databases through injected database connections, enabling data persistence and retrieval operations](https://res.cloudinary.com/dza7lstvk/image/upload/v1759761866/Medusa%20Book/db-layer-new_faeksx.jpg)