Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
slug: /concepts/working-with-endpoints
sidebar_label: Working with endpoints
description: Endpoints expose server methods to a generated, typed client your Flutter app calls, with one client setup pointed at each environment.
---

# Working with endpoints
Expand Down Expand Up @@ -71,6 +72,28 @@ apiServer:

To enable browser credentials for CORS or use platform-native HTTP clients, see [Configure HTTP calls](./working-with-endpoints/configure-http-calls).

## Point the client at each environment

The URL you pass to `Client` is the server the app connects to, so it changes between development, staging, and production. Keep one client setup and choose the URL at build time with `--dart-define`:

```dart
const serverUrl = String.fromEnvironment(
'SERVERPOD_URL',
defaultValue: 'http://localhost:8080/',
);

var client = Client(serverUrl)
..connectivityMonitor = FlutterConnectivityMonitor();
```

Pass the URL for each build:

```bash
flutter run --dart-define=SERVERPOD_URL=https://staging.example.com/
```

Without the define, the app falls back to the local server. In production, use your deployed server's public URL. If you deploy to [Serverpod Cloud](/cloud), that is the URL of your deployed app.

## Passing parameters

There are some limitations to how endpoint methods can be implemented. Parameters and return types can be of type `bool`, `int`, `double`, `String`, `UuidValue`, `Duration`, `DateTime`, `ByteData`, `Uri`, `BigInt`, or generated serializable objects (see next section). A typed `Future` should always be returned. Null safety is supported. When passing a `DateTime` it is always converted to UTC.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
description: Endpoint inheritance lets one endpoint extend another, override behavior from Serverpod modules, and control which subclasses generate client code.
---

# Endpoint inheritance

Endpoints can be based on other endpoints using inheritance, like `class ChildEndpoint extends ParentEndpoint`. If the parent endpoint was marked as `abstract` or `@doNotGenerate`, no client code is generated for it, but a client will be generated for your subclass as long as it does not opt out again.
Endpoints can be based on other endpoints using inheritance, like `class ChildEndpoint extends ParentEndpoint`. If the parent endpoint was marked as `abstract` or `@doNotGenerate`, no client code is generated for it, but a client will be generated for your subclass, as long as it does not opt out again.
Inheritance gives you the possibility to modify the behavior of `Endpoint` classes defined in other Serverpod modules.

Currently, there are the following possibilities to extend another `Endpoint` class:
Expand Down Expand Up @@ -125,7 +129,7 @@ class AdderEndpoint extends Endpoint {
}
```

`AdderEndpoint` exposes `add`, but `subtract` is annotated with `@doNotGenerate` and is therefore excluded from the generated client.
The `AdderEndpoint` exposes `add`, but `subtract` is annotated with `@doNotGenerate` and is therefore excluded from the generated client.

### Hiding an inherited method

Expand Down Expand Up @@ -155,7 +159,7 @@ class AdderEndpoint extends CalculatorEndpoint {
```

Since `CalculatorEndpoint` is annotated with `@doNotGenerate`, it will not be exposed on the server, and no client class is generated for it. `AdderEndpoint` inlines the inherited methods directly, and since it re-declares `subtract` with `@doNotGenerate`, only `add` is exposed on the generated client.
Don't worry about the exception in the `subtract` implementation. That is only added to satisfy the Dart compiler – in practice, nothing will ever call this method on `AdderEndpoint`.
Don't worry about the exception in the `subtract` implementation. That is only added to satisfy the Dart compiler. In practice, nothing will ever call this method on `AdderEndpoint`.

:::warning
If the parent class is only `abstract` (and not itself annotated with `@doNotGenerate`), Serverpod still generates an abstract client class that mirrors it, and every subclass's generated client must implement all of its methods. In that case, `@doNotGenerate` **cannot** be used to hide an inherited method. Doing so removes the method from the subclass's generated client while the abstract client class still declares it, which causes a Dart compile error ("missing concrete implementation"). To hide an inherited method, the parent class must be marked `@doNotGenerate` itself, not just `abstract`.
Expand Down Expand Up @@ -322,7 +326,7 @@ var advancedCalc = client.getEndpointOfType<EndpointCalculator>('advancedCalcula

#### Use case: Module-provided abstract endpoints

This pattern is particularly powerful for modules. A module can provide an abstract endpoint that defines an interface, and users of the module can extend it to expose the functionality on their server:
This pattern is especially useful for modules. A module can provide an abstract endpoint that defines an interface, and users of the module can extend it to expose the functionality on their server:

**In a module (e.g., `serverpod_auth`):**

Expand Down Expand Up @@ -374,7 +378,7 @@ class UserLoggedInWidget extends StatelessWidget {

**In the user application:**

The user will just have to extend the abstract endpoint to expose it on their server. Then, any client code that depends on the abstract endpoint will work seamlessly, regardless of the concrete class name or location.
The user will just have to extend the abstract endpoint to expose it on their server. Then, any client code that depends on the abstract endpoint will work regardless of the concrete class name or location.

```dart
// Extend the module's abstract endpoint to expose it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,28 @@ final client = Client(
httpClientOverride: createHttpClient(),
);
```

The stub file provides a fallback for platforms without `dart:io` (such as web), where the default client is used:

```dart title="src/http_client_stub.dart"
import 'package:http/http.dart' as http;

http.Client? createHttpClient() => null;
```

The `dart:io` implementation wraps the platform-native client from the example above in a top-level `createHttpClient()` function:

```dart title="src/http_client_io.dart"
import 'dart:io';

// Same imports as the platform-native example above.

http.Client? createHttpClient() {
if (Platform.isAndroid) {
// ... return the CronetClient shown above
} else if (Platform.isIOS || Platform.isMacOS) {
// ... return the CupertinoClient shown above
}
return null;
}
```
15 changes: 5 additions & 10 deletions docs/06-concepts/01-working-with-endpoints/04-middleware.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
description: Add middleware to Serverpod to intercept HTTP requests and responses for concerns such as logging, caching, and rate limiting.
sidebar_label: Endpoint middleware
description: Middleware on the API server intercepts endpoint requests and responses for concerns such as logging, caching, and rate limiting.
---

# Middleware
# Endpoint middleware

Middleware runs before and after your endpoints, making it suitable for logging, caching, and rate limiting. Serverpod middleware follows the [Relic middleware](https://docs.dartrelic.dev/reference/middleware) interface.
Middleware runs before and after your endpoints, making it suitable for logging, caching, and rate limiting. Serverpod middleware follows the [Relic middleware](https://docs.dartrelic.dev/reference/middleware) interface. To add middleware to web server routes instead, scoped to path prefixes, see [Web server middleware](../webserver/middleware).

## Adding middleware to your server

Expand Down Expand Up @@ -91,10 +92,4 @@ Middleware errorHandlingMiddleware() {
}
```

## Best practices

1. **Order matters**: Add middleware in the order you want it to execute.

2. **Performance**: Middleware executes on every request, so keep it efficient.

3. **Test your middleware**: Write tests to verify middleware behavior in isolation and when composed.
Middleware runs on every request in the order it is added, so keep each one focused and efficient.
40 changes: 39 additions & 1 deletion docs/06-concepts/04-exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Handle server errors in Serverpod by defining serializable exceptio

Serverpod allows you to throw an exception on the server, serialize it, and catch it in your client app.

If you throw a normal exception that isn't caught by your code, it will be treated as an internal server error. The exception will be logged together with its stack trace, and a 500 HTTP status (internal server error) will be sent to the client. On the client side, this will throw a non-specific ServerpodException, which provides no more data than a session id number which can help identify the call in your logs.
If you throw a normal exception that isn't caught by your code, it will be treated as an internal server error. The exception will be logged together with its stack trace, and a 500 HTTP status (internal server error) will be sent to the client. On the client side, this throws a `ServerpodClientException` with status code 500 (specifically `ServerpodClientInternalServerError`). The error message and stack trace stay on the server, logged to the `serverpod_session_log` table.

:::tip
Use the Serverpod Insights app to view your logs. It will show any failed or slow calls and will make it easy to pinpoint any errors in your server.
Expand Down Expand Up @@ -116,3 +116,41 @@ fields:
message: String, default="An error occurred"
errorCode: int, default=1001
```

## Handling errors on the client

A call from the client can fail in three ways, and you usually handle each one differently:

- A **serializable exception you defined** (`MyException` above): a known, app-level failure. Catch it by its type and show the reader what happened.
- A **`ServerpodClientException`**: something went wrong in the communication or on the server. Its typed subclasses map to HTTP status codes: `ServerpodClientBadRequest` (400), `ServerpodClientUnauthorized` (401), `ServerpodClientForbidden` (403), `ServerpodClientNotFound` (404), and `ServerpodClientInternalServerError` (500).
- A **connection failure**: when the app cannot reach the server (offline, wrong URL, or a timeout), it throws a `ServerpodClientException` with a `statusCode` of `-1`.

Catch the specific cases first, then fall back to the general one:

```dart
try {
await client.example.doThingy();
} on MyException catch (e) {
// A failure you defined and threw on the server.
showError(e.message);
} on ServerpodClientUnauthorized catch (_) {
// The call requires the user to sign in.
redirectToSignIn();
} on ServerpodClientException catch (e) {
if (e.statusCode == -1) {
// Could not reach the server.
showError('Cannot reach the server. Check your connection and try again.');
} else {
// The server returned an error, for example a 500.
showError('Something went wrong. Please try again.');
}
}
```

## Don't leak sensitive data

Only the serializable exceptions you define reach the client, and every field on them is sent as-is. An uncaught exception becomes a generic 500 with no details, so internal errors never leak on their own. The risk is what you put on the exceptions you do send.

- Don't put stack traces, secrets, database IDs, or internal messages into serializable exception fields. Send only what the reader should see.
- Write user-facing messages, and keep the diagnostic detail in your server logs where you can look it up later.
- Validate and sanitize input before acting on it, so a bad request fails cleanly instead of surfacing an internal error.
2 changes: 1 addition & 1 deletion docs/06-concepts/12-file-uploads.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Serverpod can use Google Cloud Storage's HMAC interoperability (S3-compatible) t

1. Create a service account with the _Storage Admin_ role.
2. Under _Cloud Storage_ > _Settings_ > _Interoperability_, create a new HMAC key for your newly created service account.
3. Add the two keys you received in the previous step to your `config/password.yaml` file. The keys should be named `HMACAccessKeyId` and `HMACSecretKey`, respectively. You can also pass them in as environment variables. The environment variable names are `SERVERPOD_HMAC_ACCESS_KEY_ID` and `SERVERPOD_HMAC_SECRET_KEY`.
3. Add the two keys you received in the previous step to your `config/passwords.yaml` file. The keys should be named `HMACAccessKeyId` and `HMACSecretKey`, respectively. You can also pass them in as environment variables. The environment variable names are `SERVERPOD_HMAC_ACCESS_KEY_ID` and `SERVERPOD_HMAC_SECRET_KEY`.
4. When creating a new bucket, set the _Access control_ to _Fine-grained_ and disable the _Prevent public access_ option.

You may also want to add the bucket as a backend for your load balancer to give it a custom domain name.
Expand Down
70 changes: 23 additions & 47 deletions docs/06-concepts/15-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,63 +128,39 @@ In the example above, the client sends an error to the server, which then throws

Read more about serializable exceptions here: [Serializable exceptions](exceptions).

## Streaming Endpoints (Deprecated)
## Example: live updates for a filtered query

:::warning
Streaming Endpoints are deprecated and will be removed in a future version of Serverpod. Use [Streaming Methods](#streaming-methods) instead for a simpler and more robust streaming experience.
:::

Streaming endpoints were Serverpod's first attempt at streaming data. This approach is more manual, requiring developers to manage the WebSocket connection to the server.

### Handling streams server-side

The Endpoint class has three methods you override to work with streams.

- `streamOpened` is called when a user connects to a stream on the Endpoint.
- `streamClosed` is called when a user disconnects from a stream on the Endpoint.
- `handleStreamMessage` is called when a serialized message is received from a client.

To send a message to a client, call the `sendStreamMessage` method. You will need to include the session associated with the user.

#### The user object

Associate state with a streaming session by setting a user object when the stream is opened.

```dart
Future<void> streamOpened(StreamingSession session) async {
setUserObject(session, MyUserObject());
}
```

You can access the user object at any time by calling the `getUserObject` method. The user object is automatically discarded when a session ends.

### Handling streams in your app

Before you can access streams in your client, you need to connect to the server's web socket by calling `openStreamingConnection` on your client.
A common real-time need is to push updates for one record or filter, for example the messages in a single chat room. Combine a streaming method with [server events](./server-events): the streaming method subscribes the client to a channel scoped to that filter, and whatever changes the data posts to the same channel.

```dart
await client.openStreamingConnection();
```

You can monitor the state of the connection by adding a listener to the client.
Once connected to your server's web socket, you can pass and receive serialized objects.

Listen to its web socket stream to receive updates from an endpoint on the server.
class ChatEndpoint extends Endpoint {
// The client subscribes to live messages for one room.
Stream<ChatMessage> watchRoom(Session session, int roomId) async* {
yield* session.messages.createStream<ChatMessage>('room_$roomId');
}

```dart
await for (var message in client.myEndpoint.stream) {
_handleMessage(message);
// Posting a message stores it and broadcasts it to everyone watching the room.
Future<void> postToRoom(Session session, int roomId, String text) async {
var message = ChatMessage(roomId: roomId, text: text);
await ChatMessage.db.insertRow(session, message);
await session.messages.postMessage('room_$roomId', message);
}
}
```

You send messages to the server's endpoint by calling `sendStreamMessage`.
On the client, listen to the returned stream and call `postToRoom` to publish:

```dart
client.myEndpoint.sendStreamMessage(MyMessage(text: 'Hello'));
var messages = client.chat.watchRoom(roomId);
messages.listen((message) {
print('Room ${message.roomId}: ${message.text}');
});

await client.chat.postToRoom(roomId, 'Hello, room!');
```

:::info
Because the channel name includes the `roomId`, each client receives updates only for the room it is watching. The same pattern works for any filter: scope the channel by the id or query you care about, and post to it whenever the data changes. To fan the updates out across multiple server instances, post with `global: true` (see [Global messages](./server-events#global-messages)).

Authentication is handled automatically. If you have signed in, your web socket connection will be authenticated.
## Streaming endpoints (deprecated)

:::
Serverpod's original streaming API (`streamOpened`, `handleStreamMessage`, `sendStreamMessage`, `openStreamingConnection`) is deprecated and will be removed in a future version. Use [streaming methods](#streaming-methods) instead. The old API is kept for reference in [Streaming endpoints (deprecated)](../upgrading/archive/streaming-endpoints).
64 changes: 64 additions & 0 deletions docs/11-upgrading/02-archive/07-streaming-endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
description: The deprecated streaming endpoints API (streamOpened, handleStreamMessage, sendStreamMessage, openStreamingConnection). Use streaming methods instead.
---

# Streaming endpoints (deprecated)

:::warning
Streaming endpoints are deprecated and will be removed in a future version of Serverpod. Use [streaming methods](../../concepts/streams#streaming-methods) instead for a simpler and more reliable streaming experience. This page is kept for projects still on the old API.
:::

Streaming endpoints were Serverpod's first attempt at streaming data. This approach is more manual, requiring you to manage the WebSocket connection to the server.

## Handling streams server-side

The Endpoint class has three methods you override to work with streams.

- `streamOpened` is called when a user connects to a stream on the Endpoint.
- `streamClosed` is called when a user disconnects from a stream on the Endpoint.
- `handleStreamMessage` is called when a serialized message is received from a client.

To send a message to a client, call the `sendStreamMessage` method. You will need to include the session associated with the user.

### The user object

Associate state with a streaming session by setting a user object when the stream is opened.

```dart
Future<void> streamOpened(StreamingSession session) async {
setUserObject(session, MyUserObject());
}
```

You can access the user object at any time by calling the `getUserObject` method. The user object is automatically discarded when a session ends.

## Handling streams in your app

Before you can access streams in your client, you need to connect to the server's web socket by calling `openStreamingConnection` on your client.

```dart
await client.openStreamingConnection();
```

You can monitor the state of the connection by adding a listener to the client.
Once connected to your server's web socket, you can pass and receive serialized objects.

Listen to its web socket stream to receive updates from an endpoint on the server.

```dart
await for (var message in client.myEndpoint.stream) {
_handleMessage(message);
}
```

You send messages to the server's endpoint by calling `sendStreamMessage`.

```dart
client.myEndpoint.sendStreamMessage(MyMessage(text: 'Hello'));
```

:::info

Authentication is handled automatically. If you have signed in, your web socket connection will be authenticated.

:::
Loading