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.
Project structure
When you run serverpod create, it produces three Dart packages in a single workspace:
my_project/
├── my_project_server/ # Your server-side code
├── my_project_client/ # Auto-generated. Never edit by hand.
└── 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.
Because the client package is auto-generated from the server code, there is no need to write serialization code, HTTP calls, or API contracts.
Code generation
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.
The generator reads two kinds of source files:
- Model files (
.spy.yaml) defining your data classes. - Endpoint classes defining your server's API.
From these files, Serverpod generates:
- A typed client in the
_clientpackage, allowing your Flutter app to call the backend with full type-safety. - Serialization and ORM classes in the
_serverpackage, for database access and communication.
Calling the backend
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.
final result = await client.greeting.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.
Request lifecycle
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.
The diagram below shows the journey of a request:
Real-time streaming
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, which keep a WebSocket connection open and let server and client push data to each other continuously.
Session
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.
Type safety across the stack
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.
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.