Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2337462
docs: Fix WebServer documentation to match actual implementation
nielsenko Nov 20, 2025
fd5b86d
docs: Add routing and middleware documentation
nielsenko Nov 20, 2025
69a2a83
docs: Add modular route documentation with injectIn override
nielsenko Nov 20, 2025
dbcd65e
docs: Add comprehensive ContextProperty documentation
nielsenko Nov 20, 2025
a70822f
docs: Add typed headers documentation
nielsenko Nov 20, 2025
094db22
docs: Add custom typed headers documentation
nielsenko Nov 20, 2025
7af9bde
docs: Add extension methods for custom typed headers
nielsenko Nov 20, 2025
205a0cd
docs: Clarify /** is tail-match wildcard with O(h) performance guarantee
nielsenko Nov 20, 2025
d1f65c3
docs: Clarify raw header access works for all headers, not just custo…
nielsenko Nov 20, 2025
0338cc5
docs: Update cache-busting documentation with CacheBustingConfig
nielsenko Nov 20, 2025
7b8141e
docs: Document automatic ETag and Last-Modified support in StaticRoute
nielsenko Nov 20, 2025
dba44cf
docs: Replace authentication examples with better ContextProperty use…
nielsenko Nov 20, 2025
9177dc8
docs: Replace authentication middleware with API key validation example
nielsenko Nov 20, 2025
4f89069
docs: Add critical clarifications for error handling and Session access
nielsenko Nov 20, 2025
a6a29fc
docs: Add error handling middleware section
nielsenko Nov 20, 2025
9ca1c25
docs: Add connecting prose to improve documentation flow
nielsenko Nov 20, 2025
c6c9451
docs: Rewrap webserver section
nielsenko Nov 20, 2025
7090f51
docs: Improve documentation based on feedback
nielsenko Nov 20, 2025
600201b
docs: Fix style issues in introduction
nielsenko Nov 20, 2025
377342b
docs: Fix code formatting broken by markdown rewrap
nielsenko Nov 20, 2025
4106f7e
docs: Rewrap with prettier (not zed)
nielsenko Nov 20, 2025
5097b11
docs: Fix hyphenation nit
nielsenko Nov 20, 2025
8060dad
docs: Use proper HTTP code blocks for conditional request examples
nielsenko Nov 20, 2025
36606c4
docs: Split WebServer documentation into multiple focused pages
nielsenko Nov 21, 2025
da476a0
docs: Use sequence diagram to explain middleware order
nielsenko Nov 21, 2025
c1a002e
docs: Nit. We are cannot generate REST apis (old mistake)
nielsenko Nov 21, 2025
be7dc48
docs: Fix lints
nielsenko Nov 21, 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
213 changes: 213 additions & 0 deletions docs/01-get-started/04-web-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
---
sidebar_label: 4. Working with the web server
---

# Working with the web server

In addition to endpoints for your Flutter app, Serverpod includes a built-in web server for REST APIs, static files, and webhooks. This is useful when you need to integrate with third-party services, serve web pages, or provide public APIs. The web server gives you full access to your database and business logic through the `Session` object, just like regular endpoints.

:::info
The web server is built on the [Relic](https://github.com/serverpod/relic) framework, which provides routing, middleware, typed headers, and more. You get the benefits of Serverpod's database integration combined with Relic's web server capabilities.
:::

## Building a REST API

Let's create a complete REST API for managing recipes with support for listing, creating, retrieving, and deleting recipes. Create a new file `magic_recipe_server/lib/src/routes/recipe_route.dart`:

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

class RecipeRoute extends Route {
// Specify which HTTP methods this route accepts
RecipeRoute() : super(methods: {Method.get, Method.post, Method.delete});

// Override injectIn to register multiple handler functions for different paths
// This is called "modular routing" and lets you organize related endpoints together
@override
void injectIn(RelicRouter router) {
router
..get('/', _list) // GET /api/recipes
..get('/:id', _get) // GET /api/recipes/123
..post('/', _create) // POST /api/recipes
..delete('/:id', _delete); // DELETE /api/recipes/123
}

Future<Result> _list(Request request) async {
// Access the Session through request.session (modular routes only get Request)
final recipes = await Recipe.db.find(request.session, limit: 10);

return Response.ok(
body: Body.fromString(
jsonEncode({'recipes': recipes.map((r) => r.toJson()).toList()}),
mimeType: MimeType.json, // Set proper content type
),
);
}

Future<Result> _get(Request request) async {
// Path parameters are accessed using symbols: pathParameters[#id]
final id = int.tryParse(request.pathParameters[#id] ?? '');
if (id == null) return Response.badRequest();

final recipe = await Recipe.db.findById(request.session, id);

// Return different status codes based on the result
if (recipe == null) return Response.notFound();

return Response.ok(
body: Body.fromString(jsonEncode(recipe.toJson()), mimeType: MimeType.json),
);
}

Future<Result> _create(Request request) async {
// Read and parse the request body
final data = jsonDecode(await request.readAsString());
final recipe = Recipe(
title: data['title'],
ingredients: data['ingredients'],
);
await Recipe.db.insertRow(request.session, recipe);

// Return 201 Created with the new resource
return Response.created(
body: Body.fromString(jsonEncode(recipe.toJson()), mimeType: MimeType.json),
);
}

Future<Result> _delete(Request request) async {
final id = int.tryParse(request.pathParameters[#id] ?? '');
if (id == null) return Response.badRequest();

await Recipe.db.deleteRow(request.session, id);

// 204 No Content is appropriate for successful DELETE
return Response.noContent();
}

// When using injectIn, handleCall is not used
@override
Future<Result> handleCall(Session session, Request request) async {
throw UnimplementedError('Uses injectIn');
}
}
```

Register the route in your `server.dart` file before calling `pod.start()`:

```dart
// Add your web routes here
pod.webServer.addRoute(RecipeRoute(), '/api/recipes');

await pod.start();
```

This creates a complete CRUD API:
- `GET /api/recipes` - List all recipes

Check failure on line 106 in docs/01-get-started/04-web-server.md

View workflow job for this annotation

GitHub Actions / markdown-lint

Lists should be surrounded by blank lines [Context: "- `GET /api/recipes` - List al..."]
- `GET /api/recipes/123` - Get a specific recipe by ID
- `POST /api/recipes` - Create a new recipe
- `DELETE /api/recipes/123` - Delete a recipe

You can test it with curl:

```bash
# List recipes
curl http://localhost:8080/api/recipes

# Get a specific recipe
curl http://localhost:8080/api/recipes/1

# Create a new recipe
curl -X POST http://localhost:8080/api/recipes \
-H "Content-Type: application/json" \
-d '{"title":"Pasta","ingredients":"Tomatoes, pasta, basil"}'

# Delete a recipe
curl -X DELETE http://localhost:8080/api/recipes/1
```

## Middleware for cross-cutting concerns

Middleware lets you add functionality that applies to multiple routes, like logging, authentication, or error handling. Middleware functions wrap your route handlers and can inspect or modify requests and responses.

```dart
// Add this before your route registrations
Handler loggingMiddleware(Handler next) {
return (Request request) async {
final start = DateTime.now();
print('→ ${request.method.name} ${request.url.path}');

// Call the next handler in the chain
final response = await next(request);

final duration = DateTime.now().difference(start);
print('← ${response.statusCode} (${duration.inMilliseconds}ms)');

return response;
};
}

// Apply to all routes
pod.webServer.addMiddleware(loggingMiddleware, '/');
```

You can add multiple middleware functions and scope them to specific paths:

```dart
// Only apply to API routes
pod.webServer.addMiddleware(authenticationMiddleware, '/api');
```

## Serving static files

For serving CSS, JavaScript, images, and other static assets, use `StaticRoute`:

```dart
import 'dart:io';

// Serve files from the web/static directory
pod.webServer.addRoute(
StaticRoute.directory(
Directory('web/static'),
// Optional: set cache control for better performance
cacheControlFactory: StaticRoute.publicImmutable(maxAge: 31536000),
),
'/static/**', // The /** wildcard matches all paths under /static/
);
```

Now files in `web/static/` are accessible at `/static/`:
- `web/static/logo.png` → `http://localhost:8080/static/logo.png`

Check failure on line 180 in docs/01-get-started/04-web-server.md

View workflow job for this annotation

GitHub Actions / markdown-lint

Lists should be surrounded by blank lines [Context: "- `web/static/logo.png` → `htt..."]
- `web/static/css/style.css` → `http://localhost:8080/static/css/style.css`

## Advanced features

The web server includes many more features for production-ready APIs:

**Typed headers** - Access headers in a type-safe way instead of raw strings:

```dart
// Instead of: request.headers['Authorization']
final auth = request.headers.authorization; // Returns AuthorizationHeader?
if (auth is BearerAuthorizationHeader) {
final token = auth.token; // Automatically parsed
}
```

**ContextProperty** - Attach request-scoped data that middleware can set and routes can read:

```dart
final requestIdProperty = ContextProperty<String>();

// In middleware: attach a request ID
requestIdProperty[request] = Uuid().v4();

// In route: access the request ID
final requestId = requestIdProperty[request];
```

**Webhooks** - Handle incoming webhooks from third-party services by validating signatures and processing events.

**Cache-busting** - Automatically version static assets with content hashes for optimal caching.

See the full [Web Server documentation](../concepts/webserver/overview) for details on [routing](../concepts/webserver/routing), [middleware](../concepts/webserver/middleware), [typed headers](../concepts/webserver/typed-headers), [static files](../concepts/webserver/static-files), and more.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_label: 4. Deploying Serverpod
sidebar_label: 5. Deploying Serverpod
---

# Deploying Serverpod
Expand Down
74 changes: 0 additions & 74 deletions docs/06-concepts/18-webserver.md

This file was deleted.

Loading