Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 79 additions & 42 deletions docs/04-get-started/03-how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,120 @@
sidebar_label: How it works
sidebar_class_name: sidebar-icon-overview
slug: /how-it-works
description: "Understand Serverpod's architecture: the three-package layout, the code generator, and the request lifecycle that gives you full-stack type safety."
description: 'Start building with Serverpod. Learn how to add endpoints, call them from Flutter, define data models, and work with hot reload across the backend and your app.'
---

# How Serverpod works

Serverpod is built around three Dart packages and a code generator, which bridges the gap between your server-side logic, your database, and your Flutter app. Together, they give you full-stack type safety from your database to your Flutter app.
With Serverpod, you write type-safe Dart on both your Flutter app and the backend. With hot reload, all the changes immediately take effect. Edit a file, hit save, and your running server, your Flutter app, your database, and the generated code that connects them update together. No manual rebuilds or restarts, no Docker to wire up, and no API code to write by hand.

## Project structure
Serverpod is a full backend. It manages your database, authentication, file uploads, caching, real-time communication, scheduling, and logging. You can focus on building features instead of wiring together separate services. A project is a single workspace of Dart packages, and a code generator keeps the types shared between your server and app in sync. If you rename a field or use an incorrect type, they will show up as compile-time errors rather than a surprise when you run the app.

When you run `serverpod create`, it produces three Dart packages in a single workspace:
## Create your project

A Serverpod project starts with the `serverpod create` command, which walks you through a few choices that shape what it generates:

- **Project type:** A full server, or a reusable [module](../concepts/modules) shared across servers.
- **Database and caching:** Add a Postgres database and Redis (for pub/sub and caching).
- **Authentication:** Built-in email and social sign-ins.
- **Web server:** Optionally serve web pages and your Flutter web app alongside your API.
- **AI agent / Code editor (optional):** The coding agent you build with (Claude, Cursor, or VS Code), set up with agent skills.

The result is one workspace with three Dart packages:

```text
my_project/
├── my_project_server/ # Your server-side code
├── my_project_client/ # Auto-generated. Never edit by hand.
├── my_project_server/ # Your backend code.
├── my_project_client/ # Generated client code used by the app.
└── my_project_flutter/ # Your Flutter app.
```

The `_server` package holds your backend code, while the `_client` package acts as a bridge, providing the Flutter app with a typed API to call the server.
In the `_server` package you add your endpoints and data models. Serverpod's code generator generates code on the server and in the client package. You get a type-safe Dart API for your app, along with the serialization and database code on the server. You never write serialization, HTTP calls, or API contracts.

Because the client package is auto-generated from the server code, there is no need to write serialization code, HTTP calls, or API contracts.
## Run your project

## Code generation
You run the whole project with one command:

```bash
$ serverpod start
```

Code generation cuts boilerplate and keeps types in sync between server and app. Serverpod watches your `_server` package as you edit and runs the generator automatically.
This starts your server, launches your Flutter app, and watches your code. When you make a change, Serverpod instantly regenerates the client, hot reloads the server, and reloads your app.

The generator reads two kinds of source files:
:::tip

- **Model files (`.spy.yaml`)** defining your data classes.
- **Endpoint classes** defining your server's API.
Start your project before you begin building. With `serverpod start` already running, every change after that, whether you make it yourself or an agent does it, is reloaded as you go.

From these files, Serverpod generates:
:::

## Write an endpoint

In Serverpod, endpoints are the entry points your app calls to run code on the server. You define one as a class that extends `Endpoint`, with async methods that each take a [`Session`](../concepts/sessions) as their first argument and return a typed `Future`:

```dart
class ExampleEndpoint extends Endpoint {
Future<String> hello(Session session, String name) async {
return 'Hello $name';
}
}
```

- **A typed client** in the `_client` package, allowing your Flutter app to call the backend with full type-safety.
- **Serialization and ORM classes** in the `_server` package, for database access and communication.
The `Session` is the context for that one call, with access to the database, cache, signed-in user, and logging. Parameters and return types can be the built-in types (`bool`, `int`, `double`, `String`, `DateTime`, `Duration`, `Uri`, `UuidValue`, `BigInt`, `ByteData`), a `List`, `Map`, `Set`, or `Record` of those, or any data model you define. See [Working with endpoints](../concepts/working-with-endpoints) for more information.

## Calling the backend
## Call it from your app

Thanks to the generated client, calling a server endpoint from your Flutter app feels like a local method call. You do not need to write any networking or serialization code.
On the app side, the generated client turns each endpoint method into what looks like a local method:

```dart
final result = await client.greeting.hello('World');
final greeting = await client.example.hello('World');
```

This example calls the `hello` method on the `greeting` endpoint. The generated client handles the JSON serialization, HTTP request, and response deserialization automatically.
The client handles the request, the response, and the JSON in between. Most calls follow this request-and-response shape. For live updates, Serverpod also has [streaming endpoints](../concepts/streams) that keep a connection open so the server and app can push data to each other.

## Define your data models

## Request lifecycle
Models are the data classes your app and server pass back and forth, the objects your endpoints take and return. You define them in `.spy.yaml` files, each naming a class and listing its fields:

When your Flutter app calls a server method, the generated client serializes the request and sends it to the server. Serverpod uses a protocol similar to JSON-RPC, which makes remote method calls feel like local function calls instead of traditional REST requests.
```yaml
class: Company
fields:
name: String
foundedDate: DateTime?
```

The diagram below shows the journey of a request:
When you generate code, this becomes a `Company` Dart class shared by the server and your app, so the same type flows from your endpoints into your Flutter widgets. Fields support the same types as endpoint methods, plus enums and nested models, and can be nullable. See [Working with models](../concepts/models) for the details.

```mermaid
sequenceDiagram
participant App as Flutter app
participant Client as Generated client
participant Server as Serverpod server
participant Endpoint as Your endpoint
Add a `table` key to also store the model in a database table:

App->>Client: client.endpoint.method(args)
Client->>Server: HTTP request (JSON)
Server->>Endpoint: method(session, args)
Endpoint-->>Server: result
Server-->>Client: HTTP response (JSON)
Client-->>App: typed Dart object
```yaml
class: Company
table: company
fields:
name: String
foundedDate: DateTime?
```

### Real-time streaming
The generated `Company` class then gains a `db` field with type-safe methods for reading and writing rows, so you query the database in Dart instead of SQL. For example, insert a row and read it back:

```dart
final company = await Company.db.insertRow(session, Company(name: 'Serverpod'));
final stored = await Company.db.findById(session, company.id);
```

These run on the same `session` your endpoint method receives. When you change a table model, press `M` in the `serverpod start` terminal to create a migration, then `A` to apply it. Pending migrations also apply on startup.

That database runs without setup on your part: Serverpod manages an embedded Postgres for you, with no Docker to configure. If you would rather manage Postgres yourself, you can change the configuration in the server's `config` directory.

See [Working with the database](../concepts/database/crud) for building queries, relations, and transactions.

Regular endpoint methods follow the request/response lifecycle above. For real-time use cases like live updates, collaborative features, and multiplayer, Serverpod also supports [streaming endpoints](../06-concepts/15-streams.md), which keep a WebSocket connection open and let server and client push data to each other continuously.
## Build with an AI agent

### Session
Serverpod is built to work with AI coding agents. You pick your agent when you create the project, and Serverpod installs skills that teach it how a Serverpod project fits together. While `serverpod start` runs, it exposes an MCP server that lets the agent work with your live project. It can write endpoints and models, manage the database, and reload the app as it goes. You describe the feature you want, and the agent builds it.

The `Session` parameter that every endpoint method receives is the context for that single request. It gives access to the database, cache, signed-in user, and logging, all available only while the request runs. Each call gets its own `Session`.
Your data models and endpoint methods are the single source of truth. When the agent renames a field or changes its type, the regenerated code treats the mismatch as a compile-time error rather than a runtime bug.

## Type safety across the stack
If you want to quickly try out Serverpod and build something with an AI agent, check out our [Quickstart](02-quickstart.md) guide.

Type safety across the entire stack, from the database to your Flutter app, is guaranteed because Serverpod's model files (`.spy.yaml`) are the single source of truth. When code is generated, the same Dart class is used in database queries, server logic, and your Flutter app.
## Next steps

This eliminates a whole category of bugs common in traditional client-server development, such as mismatched field names, incorrect types, or forgotten null checks after an API change. If you rename a field in a model file and regenerate, the Dart compiler immediately tells you every place in both the server and the app that needs updating.
Ready to build something for real? Follow [Build your first app](../05-build-your-first-app/01-creating-endpoints.md) to create your first endpoint and call it from Flutter. When you are ready to ship, [Deploy to Serverpod Cloud](../08-deployments/01-deploy-to-serverpod-cloud.md) takes your app to production in just a few minutes.
Loading