Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
35b4eeb
docs: Fix WebServer documentation to match actual implementation
nielsenko Nov 20, 2025
553bb2e
docs: Add routing and middleware documentation
nielsenko Nov 20, 2025
5d2168f
docs: Add modular route documentation with injectIn override
nielsenko Nov 20, 2025
ee8daa6
docs: Add comprehensive ContextProperty documentation
nielsenko Nov 20, 2025
1a3ce5b
docs: Add typed headers documentation
nielsenko Nov 20, 2025
724f172
docs: Add custom typed headers documentation
nielsenko Nov 20, 2025
37a06bd
docs: Add extension methods for custom typed headers
nielsenko Nov 20, 2025
d728c88
docs: Clarify /** is tail-match wildcard with O(h) performance guarantee
nielsenko Nov 20, 2025
5ed82a7
docs: Clarify raw header access works for all headers, not just custo…
nielsenko Nov 20, 2025
7e692a3
docs: Update cache-busting documentation with CacheBustingConfig
nielsenko Nov 20, 2025
ae55ae6
docs: Document automatic ETag and Last-Modified support in StaticRoute
nielsenko Nov 20, 2025
e324a37
docs: Replace authentication examples with better ContextProperty use…
nielsenko Nov 20, 2025
08387bd
docs: Replace authentication middleware with API key validation example
nielsenko Nov 20, 2025
e97b8a9
docs: Add critical clarifications for error handling and Session access
nielsenko Nov 20, 2025
7772476
docs: Add error handling middleware section
nielsenko Nov 20, 2025
d6abe10
docs: Add connecting prose to improve documentation flow
nielsenko Nov 20, 2025
6790882
docs: Rewrap webserver section
nielsenko Nov 20, 2025
94a9758
docs: Improve documentation based on feedback
nielsenko Nov 20, 2025
3945e94
docs: Fix style issues in introduction
nielsenko Nov 20, 2025
af17d41
docs: Fix code formatting broken by markdown rewrap
nielsenko Nov 20, 2025
e325afd
docs: Rewrap with prettier (not zed)
nielsenko Nov 20, 2025
e729c0b
docs: Fix hyphenation nit
nielsenko Nov 20, 2025
d9859d4
docs: Use proper HTTP code blocks for conditional request examples
nielsenko Nov 20, 2025
1d98f2e
docs: Split WebServer documentation into multiple focused pages
nielsenko Nov 21, 2025
8ce6663
docs: Use sequence diagram to explain middleware order
nielsenko Nov 21, 2025
1c523b7
docs: Nit. We are cannot generate REST apis (old mistake)
nielsenko Nov 21, 2025
f83a090
docs: Fix lints
nielsenko Nov 21, 2025
d56a8eb
fix: Don't use title casing for titles or labels
nielsenko Nov 24, 2025
d5b8e37
fix: Remove Web server from getting started section again
nielsenko Nov 24, 2025
8d1f5a5
fix: Move modular routes as a subsection of routing
nielsenko Nov 24, 2025
fe7df57
fix: Slim down typed headers section
nielsenko Nov 24, 2025
3e2e441
fix: Drop modular route example (belongs in Relic)
nielsenko Nov 24, 2025
d882661
fix: Reduce size of module example
nielsenko Nov 24, 2025
74fc696
fix: Some re-ordering
nielsenko Nov 24, 2025
128a7ab
fix: maxAge is now a Duration. Remove overlooked duplicated section
nielsenko Nov 24, 2025
a8faf6e
fix: Prefer next over innerHandler for consistency
nielsenko Nov 24, 2025
67a0deb
ci: lint stuff
nielsenko Nov 24, 2025
681803a
fix: Talk about why fallback and /** is not the same
nielsenko Nov 24, 2025
642aab3
fix: Remove stale links to modular-routes
nielsenko Nov 24, 2025
8cd07c0
fix: Simplify, leave more to relic docs
nielsenko Dec 7, 2025
03c1942
docs: Refer to relic for more info
nielsenko Dec 7, 2025
c7fd168
fix: Make Modules section an advanced tip (signal to reader)
nielsenko Dec 7, 2025
e641afe
refactor: Split setting up routes from accessing request data
nielsenko Dec 7, 2025
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
110 changes: 0 additions & 110 deletions docs/06-concepts/06-database/09-pagination.md
Original file line number Diff line number Diff line change
@@ -1,110 +0,0 @@
# Pagination

Serverpod provides built-in support for pagination to help manage large datasets, allowing you to retrieve data in smaller chunks. Pagination is achieved using the `limit` and `offset` parameters.

## Limit

The `limit` parameter specifies the maximum number of records to return from the query. This is equivalent to the number of rows on a page.

```dart
var companies = await Company.db.find(
session,
limit: 10,
);
```

In the example we fetch the first 10 companies.

## Offset

The `offset` parameter determines the starting point from which to retrieve records. It essentially skips the first `n` records.

```dart
var companies = await Company.db.find(
session,
limit: 10,
offset: 30,
);
```

In the example we skip the first 30 rows and fetch the 31st to 40th company.

## Using limit and offset for pagination

Together, `limit` and `offset` can be used to implement pagination.

```dart
int page = 3;
int companiesPerPage = 10;

var companies = await Company.db.find(
session,
orderBy: (t) => t.id,
limit: companiesPerPage,
offset: (page - 1) * companiesPerPage,
);
```

In the example we fetch the third page of companies, with 10 companies per page.

### Tips

1. **Performance**: Be aware that while `offset` can help in pagination, it may not be the most efficient way for very large datasets. Using an indexed column to filter results can sometimes be more performant.
2. **Consistency**: Due to possible data changes between paginated requests (like additions or deletions), the order of results might vary. It's recommended to use an `orderBy` parameter to ensure consistency across paginated results.
3. **Page numbering**: Page numbers usually start from 1. Adjust the offset calculation accordingly.

## Cursor-based pagination

A limit-offset pagination may not be the best solution if the table is changed frequently and rows are added or removed between requests.

Cursor-based pagination is an alternative method to the traditional limit-offset pagination. Instead of using an arbitrary offset to skip records, cursor-based pagination uses a unique record identifier (a _cursor_) to mark the starting or ending point of a dataset. This approach is particularly beneficial for large datasets as it offers consistent and efficient paginated results, even if the data is being updated frequently.

### How it works

In cursor-based pagination, the client provides a cursor as a reference point, and the server returns data relative to that cursor. This cursor is usually an `id`.

### Implementing cursor-based pagination

1. **Initial request**:
For the initial request, where no cursor is provided, retrieve the first `n` records:

```dart
int recordsPerPage = 10;

var companies = await Company.db.find(
session,
orderBy: (t) => t.id,
limit: recordsPerPage,
);
```

2. **Subsequent requests**:
For the subsequent requests, use the cursor (for example, the last `id` from the previous result) to fetch the next set of records:

```dart
int cursor = lastCompanyIdFromPreviousPage; // This is typically sent by the client

var companies = await Company.db.find(
session,
where: Company.t.id > cursor,
orderBy: (t) => t.id,
limit: recordsPerPage,
);
```

3. **Returning the cursor**:
When returning data to the client, also return the cursor, so it can be used to compute the starting point for the next page.

```dart
return {
'data': companies,
'lastCursor': companies.last.id,
};
```

### Tips

1. **Choosing a cursor**: While IDs are commonly used as cursors, timestamps or other unique, sequentially ordered fields can also serve as effective cursors.
2. **Backward pagination**: To implement backward pagination, use the first item from the current page as the cursor and adjust the query accordingly.
3. **Combining with sorting**: Ensure the field used as a cursor aligns with the sorting order. For instance, if you're sorting data by a timestamp in descending order, the cursor should also be based on the timestamp.
4. **End of data**: If the returned data contains fewer items than the requested limit, it indicates that you've reached the end of the dataset.
74 changes: 0 additions & 74 deletions docs/06-concepts/18-webserver.md

This file was deleted.

154 changes: 154 additions & 0 deletions docs/06-concepts/18-webserver/01-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Overview

In addition to the application server, Serverpod comes with a built-in web server. The web server allows you to access your database and business layer the same way you would from a method call from an app. This makes it simple to share data for applications that need both an app and traditional web pages. You can also use the web server to create webhooks or define custom REST APIs to communicate with third-party services.

Serverpod's web server is built on the [Relic](https://github.com/serverpod/relic) framework, giving you access to its routing engine, middleware system, and typed headers. This means you get the benefits of Serverpod's database integration and business logic alongside Relic's web server capabilities.

## Your first route

When you create a new Serverpod project, it sets up a web server by default. Here's how to add a simple API endpoint:

```dart
import 'package:serverpod/serverpod.dart';

class HelloRoute extends Route {
@override
Future<Result> handleCall(Session session, Request request) async {
return Response.ok(
body: Body.fromString(
jsonEncode({'message': 'Hello from Serverpod!'}),
mimeType: MimeType.json,
),
);
}
}
```

Register the route in your `server.dart` file before starting the server:

```dart
pod.webServer.addRoute(HelloRoute(), '/api/hello');
await pod.start();
```

Visit `http://localhost:8080/api/hello` to see your API response.

## Core concepts

### Routes and handlers

A **Route** is a destination in your web server that handles requests and generates responses. Routes extend the `Route` base class and implement the `handleCall()` method:

```dart
class ApiRoute extends Route {
@override
Future<Result> handleCall(Session session, Request request) async {
// Your logic here
return Response.ok();
}
}
```

The `handleCall()` method receives:

- **Session** - Access to your database, logging, and authenticated user
- **Request** - The HTTP request with headers, body, and URL information

### Response types

Return different response types based on your needs:

```dart
// Success responses
return Response.ok(body: Body.fromString('Success'));
return Response.created(body: Body.fromString('Created'));
return Response.noContent();

// Error responses
return Response.badRequest(body: Body.fromString('Invalid input'));
return Response.unauthorized(body: Body.fromString('Not authenticated'));
return Response.notFound(body: Body.fromString('Not found'));
return Response.internalServerError(body: Body.fromString('Server error'));
```

### Adding routes

Routes are added with a path pattern:

```dart
// Exact path
pod.webServer.addRoute(UserRoute(), '/api/users');

// Path with wildcard
pod.webServer.addRoute(StaticRoute.directory(Directory('web')), '/static/**');
```

Routes are matched in the order they were added.

## When to use what

### Rest apis → custom routes

For REST APIs, webhooks, or custom HTTP handlers, use custom `Route` classes:

```dart
class UsersApiRoute extends Route {
UsersApiRoute() : super(methods: {Method.get, Method.post});

@override
Future<Result> handleCall(Session session, Request request) async {
if (request.method == Method.get) {
// List users
} else {
// Create user
}
}
}
```

See [Routing](routing) for details.

### Static files → `StaticRoute`

For serving CSS, JavaScript, images, or other static assets:

```dart
pod.webServer.addRoute(
StaticRoute.directory(Directory('web/static')),
'/static/**',
);
```

See [Static Files](static-files) for cache-busting and optimization.

## Database access

The `Session` parameter gives you full access to your Serverpod database:

```dart
class UserRoute extends Route {
@override
Future<Result> handleCall(Session session, Request request) async {
// Query database
final users = await User.db.find(session);

// Use logging
session.log('Retrieved ${users.length} users');

return Response.ok(
body: Body.fromString(
jsonEncode(users.map((u) => u.toJson()).toList()),
mimeType: MimeType.json,
),
);
}
}
```

## Next steps

- **[Routing](routing)** - Match requests to handlers by method and URL pattern
- **[Request Data](request-data)** - Access path parameters, query parameters, headers, and body
- **[Middleware](middleware)** - Intercept and transform requests and responses
- **[Static Files](static-files)** - Serve static assets
- **[Server-side HTML](server-side-html)** - Render HTML dynamically on the server
Loading