From 5a68f330f979a3be8ed429a41b6b01bed93547b1 Mon Sep 17 00:00:00 2001 From: developerjamiu Date: Mon, 6 Jul 2026 20:46:09 +0100 Subject: [PATCH] docs: Reshape Database concepts, fold ID strategy, many-to-many, and migration gaps --- docs/06-concepts/06-database/01-connection.md | 2 +- docs/06-concepts/06-database/02-models.md | 18 ++++++++- .../06-database/03-relations/01-one-to-one.md | 2 +- .../03-relations/02-one-to-many.md | 2 +- .../03-relations/03-many-to-many.md | 38 ++++++++++++++++++- .../03-relations/04-self-relations.md | 2 +- .../03-relations/05-referential-actions.md | 2 +- .../06-database/03-relations/06-modules.md | 2 +- docs/06-concepts/06-database/04-indexing.md | 2 +- docs/06-concepts/06-database/05-crud.md | 2 +- docs/06-concepts/06-database/06-filter.md | 2 +- .../06-database/07-relation-queries.md | 4 +- docs/06-concepts/06-database/08-sort.md | 2 +- .../06-database/08-transactions.md | 2 +- docs/06-concepts/06-database/09-pagination.md | 2 +- docs/06-concepts/06-database/10-raw-access.md | 2 +- docs/06-concepts/06-database/11-migrations.md | 29 +++++++++++--- .../06-database/12-runtime-parameters.md | 2 +- .../06-concepts/06-database/13-row-locking.md | 2 +- .../06-database/14-client-side-database.md | 2 +- docs/06-concepts/06-database/15-exceptions.md | 2 +- 21 files changed, 94 insertions(+), 29 deletions(-) diff --git a/docs/06-concepts/06-database/01-connection.md b/docs/06-concepts/06-database/01-connection.md index 914330a8..01333e9d 100644 --- a/docs/06-concepts/06-database/01-connection.md +++ b/docs/06-concepts/06-database/01-connection.md @@ -1,5 +1,5 @@ --- -description: Configure Serverpod's database connection details, passwords, and connect to custom Postgres or SQLite instances. +description: The database connection is defined in Serverpod's configuration and password files, with support for custom Postgres or SQLite instances. --- # Connection diff --git a/docs/06-concepts/06-database/02-models.md b/docs/06-concepts/06-database/02-models.md index 5d2c6c3d..dde0cb4f 100644 --- a/docs/06-concepts/06-database/02-models.md +++ b/docs/06-concepts/06-database/02-models.md @@ -1,5 +1,5 @@ --- -description: Map serializable models to database tables, with support for custom ID types, non-persistent fields, JSONB storage, and column name overrides. +description: Table models map serializable classes to database tables in Serverpod, with custom ID types, non-persistent fields, JSONB storage, and column name overrides. --- # Models @@ -13,7 +13,9 @@ fields: name: String ``` -When the `table` keyword is added to the model, the `serverpod generate` command will generate new methods for [interacting](crud) with the database. The addition of the keyword will also be detected by the `serverpod create-migration` command that will generate the necessary [migrations](migrations) needed to update the database. +When the `table` keyword is added to the model, Serverpod generates new methods for [interacting](crud) with the database. The keyword is also picked up by the `serverpod create-migration` command, which generates the [migrations](migrations) needed to update the database. + +For the full list of keywords you can use in a model file, see the [Model reference](../lookups/model-reference). :::info When you add a `table` to a serializable class, Serverpod will automatically add an `id` field of type `int?` to the class. You should not define this field yourself. The `id` is set when you interact with an object stored in the database. @@ -125,6 +127,16 @@ When enabled, all serializable fields across all models default to `jsonb` unles If you change the `serializationDataType` between `json` and `jsonb` at any level, the migration system will convert existing columns automatically with no data loss. +## Choosing an ID strategy + +Serverpod supports two id types: `int` (the default) and `UuidValue`. The right choice depends on how the id is generated and where it is exposed. + +An `int` id defaults to `serial`, which means the database assigns an auto-incrementing integer when the row is inserted. Serial ids are compact and sequential, which keeps indexes efficient and makes them a good fit for internal references. The trade-offs are that the id only exists after the row is saved, and the values are guessable and enumerable, so a sequential id in a public URL can leak how many records you have or let someone probe for others. + +A `UuidValue` id is a 128-bit random value. It is not enumerable, and it can be generated before the row is inserted, which suits ids that appear in public URLs, are created offline on the client, or are merged from several sources. UUIDs are larger than integers and random rather than sequential, so index locality is slightly worse than with serial ids. + +As a rule of thumb, use the default `int` serial id for internal tables, and reach for `UuidValue` when the id is public-facing, needs to exist before the row is stored, or is created across more than one system. Because a UUID can be generated on the client, never treat a client-supplied id as proof that the caller owns a record; always check that the authenticated user is allowed to use it. + ## Change ID type Changing the type of the `id` field allows you to customize the identifier type for your database tables. This is done by declaring the `id` field on table models with one of the supported types. If the field is omitted, the id field will still be created with type `int` by default. @@ -167,6 +179,8 @@ fields: When using `defaultModel=random`, the UUID will be generated when the object is created. Since an id is always assigned the `id` field can be non-nullable. +To have the server assign the id instead of the client, use `defaultPersist=random` without `defaultModel`, so the value is set on insert rather than on the client. + ## Column name override By default, the column name in the database is the same as the name of the field in the model. However, you can override this to use a different name by using the `column` keyword. For example, if you have a model property named `userName` you might want to store it in the database as `user_name`. diff --git a/docs/06-concepts/06-database/03-relations/01-one-to-one.md b/docs/06-concepts/06-database/03-relations/01-one-to-one.md index 537fd537..c98b23b0 100644 --- a/docs/06-concepts/06-database/03-relations/01-one-to-one.md +++ b/docs/06-concepts/06-database/03-relations/01-one-to-one.md @@ -1,5 +1,5 @@ --- -description: Define one-to-one relationships between database tables using Serverpod's relation keyword, with support for object fields, ID fields, and bidirectional relations. +description: A one-to-one relationship links two tables through Serverpod's relation keyword, with object fields, ID fields, and bidirectional access. --- # One-to-one diff --git a/docs/06-concepts/06-database/03-relations/02-one-to-many.md b/docs/06-concepts/06-database/03-relations/02-one-to-many.md index 673261dc..292e23b3 100644 --- a/docs/06-concepts/06-database/03-relations/02-one-to-many.md +++ b/docs/06-concepts/06-database/03-relations/02-one-to-many.md @@ -1,5 +1,5 @@ --- -description: Define one-to-many relationships between database tables using Serverpod's relation keyword, with implicit and explicit definition options. +description: A one-to-many relationship links a parent row to many child rows through Serverpod's relation keyword, with implicit and explicit options. --- # One-to-many diff --git a/docs/06-concepts/06-database/03-relations/03-many-to-many.md b/docs/06-concepts/06-database/03-relations/03-many-to-many.md index 5369eae2..c157c738 100644 --- a/docs/06-concepts/06-database/03-relations/03-many-to-many.md +++ b/docs/06-concepts/06-database/03-relations/03-many-to-many.md @@ -1,12 +1,18 @@ --- -description: Define many-to-many relationships between database tables using a junction model that holds foreign keys to both sides of the relation. +description: A many-to-many relationship connects multiple records on each side through a junction model that holds foreign keys to both tables. --- # Many-to-many Many-to-many (n:m) relationships describes a scenario where multiple records from a table can relate to multiple records in another table. An example of this would be the relationship between students and courses, where a single student can enroll in multiple courses, and a single course can have multiple students. -The Serverpod framework supports these complex relationships by explicitly creating a separate model, often called a junction or bridge table, that records the relation. +The Serverpod framework supports these relationships by explicitly creating a separate model, often called a junction or bridge table, that records the relation. + +## When to use a junction table + +Reach for a many-to-many relation when records on both sides can each relate to many records on the other, like students and courses. If a record on one side belongs to only one record on the other, a [one-to-many](one-to-many) relation is simpler. + +A junction model also gives you a place to store data about the relationship itself. If the link between two records needs a date, a grade, or a status, those fields live on the junction model. A relationship that carries its own data always needs an explicit junction table, even when a plainer relation might otherwise do. ## Overview @@ -60,3 +66,31 @@ indexes: ``` The unique index on the combination of `studentId` and `courseId` ensures that a student can only be enrolled in a particular course once. If omitted a student would be allowed to be enrolled in the same course multiple times. + +## Managing junction records + +To link a student to a course, create an `Enrollment` row with both foreign keys set and insert it: + +```dart +var enrollment = Enrollment( + studentId: student.id!, + courseId: course.id!, +); +await Enrollment.db.insertRow(session, enrollment); +``` + +The `studentId` and `courseId` fields are the foreign keys Serverpod generated from the two relations. To remove the link, delete the junction row: + +```dart +await Enrollment.db.deleteRow(session, enrollment); +``` + +Because `Student` and `Course` each hold a one-to-many relation to `Enrollment`, the generated `attach` and `detach` methods also operate on those relations. See [relation queries](../relation-queries#update) for the attach and detach API. + +## Common errors + +A few mistakes come up often when setting up a junction table: + +- **Referencing the generated foreign-key fields.** Defining `student: Student?, relation(...)` generates an implicit `studentId` column on the junction table. Use these generated `Id` names in indexes and filters, as the unique index above does. There is no separate field for you to declare. +- **Relation names must match on both sides.** The `name` argument pairs the two halves of a relation, so `course_enrollments` on `Course.enrollments` has to match `course_enrollments` on `Enrollment.course`. A mismatch is reported as a missing named relation when you run code generation. +- **Keep the relation fields persisted.** The relation fields on the junction are database columns. Marking one `!persist` drops the column, so the foreign key it backs can no longer be stored or queried. diff --git a/docs/06-concepts/06-database/03-relations/04-self-relations.md b/docs/06-concepts/06-database/03-relations/04-self-relations.md index 78e23dae..163f302f 100644 --- a/docs/06-concepts/06-database/03-relations/04-self-relations.md +++ b/docs/06-concepts/06-database/03-relations/04-self-relations.md @@ -1,5 +1,5 @@ --- -description: Define self-referential relationships where a table has a foreign key pointing to its own primary key, supporting one-to-one, one-to-many, and many-to-many patterns. +description: A self-referential relationship points a table's foreign key back to its own primary key, supporting one-to-one, one-to-many, and many-to-many patterns. --- # Self-relations diff --git a/docs/06-concepts/06-database/03-relations/05-referential-actions.md b/docs/06-concepts/06-database/03-relations/05-referential-actions.md index b8a1e4d8..027ce2a1 100644 --- a/docs/06-concepts/06-database/03-relations/05-referential-actions.md +++ b/docs/06-concepts/06-database/03-relations/05-referential-actions.md @@ -1,5 +1,5 @@ --- -description: Control how updates and deletes propagate across related tables using the onUpdate and onDelete referential action properties. +description: Referential actions control how updates and deletes propagate across related tables through the onUpdate and onDelete properties. --- # Referential actions diff --git a/docs/06-concepts/06-database/03-relations/06-modules.md b/docs/06-concepts/06-database/03-relations/06-modules.md index 33d237be..4d3696d2 100644 --- a/docs/06-concepts/06-database/03-relations/06-modules.md +++ b/docs/06-concepts/06-database/03-relations/06-modules.md @@ -1,5 +1,5 @@ --- -description: Create relations between your own database tables and tables from Serverpod modules using a bridge table approach. +description: Module relations connect your own database tables to tables from Serverpod modules through a bridge table. --- # Relations with modules diff --git a/docs/06-concepts/06-database/04-indexing.md b/docs/06-concepts/06-database/04-indexing.md index 7b5ca23d..87014ec9 100644 --- a/docs/06-concepts/06-database/04-indexing.md +++ b/docs/06-concepts/06-database/04-indexing.md @@ -1,5 +1,5 @@ --- -description: Add indexes to Serverpod database tables to improve query performance, including unique, GIN, HNSW, and IVFFLAT vector indexes. +description: Indexes on Serverpod database tables improve query performance, including unique, GIN, HNSW, and IVFFLAT vector indexes. --- # Indexing diff --git a/docs/06-concepts/06-database/05-crud.md b/docs/06-concepts/06-database/05-crud.md index 8baeed63..9d9dc36c 100644 --- a/docs/06-concepts/06-database/05-crud.md +++ b/docs/06-concepts/06-database/05-crud.md @@ -1,5 +1,5 @@ --- -description: Create, read, update, and delete database rows in Serverpod using the generated static db methods on your model classes. +description: The generated db methods on each model class create, read, update, and delete database rows in Serverpod. --- # CRUD diff --git a/docs/06-concepts/06-database/06-filter.md b/docs/06-concepts/06-database/06-filter.md index 7602623a..1d8e3197 100644 --- a/docs/06-concepts/06-database/06-filter.md +++ b/docs/06-concepts/06-database/06-filter.md @@ -1,5 +1,5 @@ --- -description: Build type-safe database filter expressions in Serverpod using column operations, logical operators, and relation filters. +description: Database filter expressions in Serverpod are type-safe, built from column operations, logical operators, and relation filters. --- # Filter diff --git a/docs/06-concepts/06-database/07-relation-queries.md b/docs/06-concepts/06-database/07-relation-queries.md index afe6155d..c7a3fcb9 100644 --- a/docs/06-concepts/06-database/07-relation-queries.md +++ b/docs/06-concepts/06-database/07-relation-queries.md @@ -1,5 +1,5 @@ --- -description: Query, filter, sort, and paginate relational data in Serverpod using include, includeList, attach, and detach methods. +description: Relation queries in Serverpod fetch, filter, sort, and paginate related data through include, includeList, attach, and detach methods. --- # Relation queries (Joins) @@ -174,7 +174,7 @@ var user = await Company.db.findById( The example above retrieves the next 10 employees starting from the 11th record: -Using these methods in conjunction provides a powerful way to query, filter, and sort relational data efficiently. +Combined, these methods let you query, filter, and sort relational data efficiently. ## Update diff --git a/docs/06-concepts/06-database/08-sort.md b/docs/06-concepts/06-database/08-sort.md index de8508e1..8824c2cd 100644 --- a/docs/06-concepts/06-database/08-sort.md +++ b/docs/06-concepts/06-database/08-sort.md @@ -1,5 +1,5 @@ --- -description: Sort database query results by one or more columns in Serverpod using orderBy and orderByList, with support for relational fields and aggregated counts. +description: Serverpod sorts query results by one or more columns with orderBy and orderByList, including relational fields and aggregated counts. --- # Sort diff --git a/docs/06-concepts/06-database/08-transactions.md b/docs/06-concepts/06-database/08-transactions.md index 612f8b46..a444f3b8 100644 --- a/docs/06-concepts/06-database/08-transactions.md +++ b/docs/06-concepts/06-database/08-transactions.md @@ -1,5 +1,5 @@ --- -description: Group database operations into atomic transactions in Serverpod, with support for isolation levels, savepoints, and rollback. +description: Transactions group database operations into an atomic unit in Serverpod, with isolation levels, savepoints, and rollback. --- # Transactions diff --git a/docs/06-concepts/06-database/09-pagination.md b/docs/06-concepts/06-database/09-pagination.md index b076aab4..dccde00d 100644 --- a/docs/06-concepts/06-database/09-pagination.md +++ b/docs/06-concepts/06-database/09-pagination.md @@ -1,5 +1,5 @@ --- -description: Paginate database query results in Serverpod using limit and offset, or implement cursor-based pagination for frequently updated datasets. +description: Serverpod paginates query results with limit and offset, or cursor-based pagination for frequently updated datasets. --- # Pagination diff --git a/docs/06-concepts/06-database/10-raw-access.md b/docs/06-concepts/06-database/10-raw-access.md index 488933f2..93392bb7 100644 --- a/docs/06-concepts/06-database/10-raw-access.md +++ b/docs/06-concepts/06-database/10-raw-access.md @@ -1,5 +1,5 @@ --- -description: Execute raw SQL queries directly against the database using Serverpod's unsafe query methods with support for named and positional parameter binding. +description: Serverpod's unsafe query methods run raw SQL directly against the database, with named and positional parameter binding. --- # Raw access diff --git a/docs/06-concepts/06-database/11-migrations.md b/docs/06-concepts/06-database/11-migrations.md index 04418de8..2e2a9c84 100644 --- a/docs/06-concepts/06-database/11-migrations.md +++ b/docs/06-concepts/06-database/11-migrations.md @@ -1,10 +1,10 @@ --- -description: Create and apply database migrations in Serverpod to keep your schema in sync as your models evolve, including repair migrations for out-of-sync databases. +description: Database migrations keep your Serverpod schema in sync as your models evolve, with repair migrations to recover a database that has drifted out of sync. --- # Migrations -Serverpod comes bundled with a simple-to-use but powerful migration system that helps you keep your database schema up to date as your project evolves. Database migrations provide a structured way of upgrading your database while maintaining existing data. +Serverpod comes with a migration system that helps you keep your database schema up to date as your project evolves. Database migrations provide a structured way of upgrading your database while maintaining existing data. A migration is a set of database operations (e.g. creating a table, adding a column, etc.) required to update the database schema to match the requirements of the project. Each migration handles both initializing a new database and rolling an existing one forward from a previous state. @@ -69,11 +69,18 @@ This would create a migration named `-v1-0-0`: │ └── 20231205080937028-v1-0-0 ``` -### Add data in a migration +### Writing custom SQL -Since the migrations are written in SQL, it is possible to add data to the database in a migration. This can be useful if you want to add initial data to the database. +Migrations are plain SQL, so you can edit a generated `migration.sql` to do work the generator does not produce on its own, such as seeding reference data, backfilling a new column, or transforming existing data alongside a schema change. -The developer is responsible for ensuring that any added SQL statements are compatible with the database schema and rolling forward from the previous migrations. +Open the `migration.sql` file in the migration's directory and add your statements. Serverpod applies the file as-is when it rolls the database forward. + +Custom SQL is worth reaching for when: + +- Initial or reference data needs to be in place as soon as the schema exists. +- A schema change requires moving or transforming existing data, for example splitting one column into two. + +You own the correctness of anything you add. Make sure it matches the schema at that point in the migration history and rolls forward cleanly from the previous migration. Serverpod does not validate hand-written SQL. Once a migration has been applied, treat it as immutable and create a new migration for further changes instead of editing it. ### Migrations directory structure @@ -100,7 +107,7 @@ For each migration, five files are created: ### During development -`serverpod start` applies your migrations for you. With the `serverpod start` terminal focused: +The `serverpod start` command applies your migrations for you. With its terminal focused: - Pending migrations are applied automatically on the first boot. - Press **A** to apply new migrations as you create them. @@ -123,6 +130,10 @@ This is useful if migrations are applied as part of an automated process. If migrations are applied at the same time as repair migration, the repair migration is applied first. +### On Serverpod Cloud + +Serverpod Cloud applies migrations for you. Every deploy starts the server with `--apply-migrations`, so any pending migrations run before the server serves requests. If a migration fails, the deploy fails; fix the migration and redeploy. See [Cloud database](/cloud/concepts/database#migrations-run-on-every-deploy) for the full flow. + ## Creating a repair migration If the database has been manually modified the database schema may be out of sync with the migration system. In this case, a repair migration can be created to bring the database schema up to date with the migration system. @@ -235,3 +246,9 @@ Then apply the repair migration, any repair migration will only be applied once: ```bash $ dart run bin/main.dart --apply-repair-migration ``` + +## Troubleshooting + +**Check the flag name when migrations do not apply.** The flag is `--apply-migrations`, plural. Passing the singular `--apply-migration` fails to parse, so Serverpod logs `Failed to parse command line arguments. Using default values.` and falls back to defaults. Migrations are not applied, and any other flags on the same command are dropped as well. If migrations are not running, check the flag spelling first. + +**Deleting a migration does not undo it.** Removing a migration directory does not roll the schema back. To undo a migration, create a repair migration targeting the version you want to return to and apply it, as described in [Rolling back migrations](#rolling-back-migrations). Only the schema is reverted; data is not. diff --git a/docs/06-concepts/06-database/12-runtime-parameters.md b/docs/06-concepts/06-database/12-runtime-parameters.md index b51561b3..d2835062 100644 --- a/docs/06-concepts/06-database/12-runtime-parameters.md +++ b/docs/06-concepts/06-database/12-runtime-parameters.md @@ -1,5 +1,5 @@ --- -description: Fine-tune PostgreSQL query behavior per transaction or globally using Serverpod's runtime parameters, particularly for vector similarity search optimization. +description: Runtime parameters tune PostgreSQL query behavior per transaction or globally in Serverpod, notably for vector similarity search. --- # Runtime parameters diff --git a/docs/06-concepts/06-database/13-row-locking.md b/docs/06-concepts/06-database/13-row-locking.md index 325baeeb..986ad3d3 100644 --- a/docs/06-concepts/06-database/13-row-locking.md +++ b/docs/06-concepts/06-database/13-row-locking.md @@ -1,5 +1,5 @@ --- -description: Lock specific database rows within a transaction in Serverpod to safely handle concurrent updates and prevent conflicting writes. +description: Row locking holds specific database rows within a transaction in Serverpod, so concurrent updates do not conflict. --- # Row locking diff --git a/docs/06-concepts/06-database/14-client-side-database.md b/docs/06-concepts/06-database/14-client-side-database.md index 41c5ef54..0934951a 100644 --- a/docs/06-concepts/06-database/14-client-side-database.md +++ b/docs/06-concepts/06-database/14-client-side-database.md @@ -1,5 +1,5 @@ --- -description: Use a SQLite database on the Flutter client with the same ORM interface as the server, including CRUD, relations, and transactions. +description: The client-side database runs SQLite on the Flutter client with the same ORM interface as the server, including CRUD, relations, and transactions. --- # Client-side database diff --git a/docs/06-concepts/06-database/15-exceptions.md b/docs/06-concepts/06-database/15-exceptions.md index 9beea73f..3d0cd674 100644 --- a/docs/06-concepts/06-database/15-exceptions.md +++ b/docs/06-concepts/06-database/15-exceptions.md @@ -1,5 +1,5 @@ --- -description: Serverpod wraps database failures in typed exceptions. Learn when each database exception is thrown and how to handle them. +description: Serverpod wraps database failures in typed exceptions that signal when a query fails and how your code should handle it. --- # Database exceptions