From cffff4e4296f7f90d10186710cd6247cb12b272e Mon Sep 17 00:00:00 2001 From: developerjamiu Date: Mon, 6 Jul 2026 12:56:25 +0100 Subject: [PATCH 1/2] docs: Reshape Endpoints & APIs concepts, archive deprecated streaming --- .../01-working-with-endpoints.md | 23 ++++++ .../02-endpoint-inheritance.md | 14 ++-- .../03-configure-http-calls.md | 25 +++++++ .../04-middleware.md | 15 ++-- docs/06-concepts/04-exceptions.md | 40 ++++++++++- docs/06-concepts/12-file-uploads.md | 2 +- docs/06-concepts/15-streams.md | 70 ++++++------------- .../02-archive/07-streaming-endpoints.md | 64 +++++++++++++++++ 8 files changed, 189 insertions(+), 64 deletions(-) create mode 100644 docs/11-upgrading/02-archive/07-streaming-endpoints.md diff --git a/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md b/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md index 26fb24ee..1513d325 100644 --- a/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md +++ b/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md @@ -1,6 +1,7 @@ --- slug: /concepts/working-with-endpoints sidebar_label: Working with endpoints +description: Define server methods as endpoints, generate a typed client, and call them from your Flutter app, including pointing the client at each environment. --- # Working with endpoints @@ -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. diff --git a/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md b/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md index 4a2b8b06..6621193e 100644 --- a/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md +++ b/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md @@ -1,6 +1,10 @@ +--- +description: Base endpoints on other endpoints with inheritance, 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: @@ -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 @@ -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`. @@ -322,7 +326,7 @@ var advancedCalc = client.getEndpointOfType('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`):** @@ -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 diff --git a/docs/06-concepts/01-working-with-endpoints/03-configure-http-calls.md b/docs/06-concepts/01-working-with-endpoints/03-configure-http-calls.md index 21abd3e0..fd3d1e55 100644 --- a/docs/06-concepts/01-working-with-endpoints/03-configure-http-calls.md +++ b/docs/06-concepts/01-working-with-endpoints/03-configure-http-calls.md @@ -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; +} +``` diff --git a/docs/06-concepts/01-working-with-endpoints/04-middleware.md b/docs/06-concepts/01-working-with-endpoints/04-middleware.md index 336b5509..590acaca 100644 --- a/docs/06-concepts/01-working-with-endpoints/04-middleware.md +++ b/docs/06-concepts/01-working-with-endpoints/04-middleware.md @@ -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: Add middleware to the API server to intercept 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 @@ -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. diff --git a/docs/06-concepts/04-exceptions.md b/docs/06-concepts/04-exceptions.md index 94b571a1..0bb8f333 100644 --- a/docs/06-concepts/04-exceptions.md +++ b/docs/06-concepts/04-exceptions.md @@ -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. @@ -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: `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. diff --git a/docs/06-concepts/12-file-uploads.md b/docs/06-concepts/12-file-uploads.md index 0d6df6ea..f2dba003 100644 --- a/docs/06-concepts/12-file-uploads.md +++ b/docs/06-concepts/12-file-uploads.md @@ -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. diff --git a/docs/06-concepts/15-streams.md b/docs/06-concepts/15-streams.md index 2d7b4772..1fce696f 100644 --- a/docs/06-concepts/15-streams.md +++ b/docs/06-concepts/15-streams.md @@ -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 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 watchRoom(Session session, int roomId) async* { + yield* session.messages.createStream('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 postToRoom(Session session, int roomId, String text) async { + var message = ChatMessage(roomId: roomId, text: text); + await ChatMessage.db.insertRow(session, message); + 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). diff --git a/docs/11-upgrading/02-archive/07-streaming-endpoints.md b/docs/11-upgrading/02-archive/07-streaming-endpoints.md new file mode 100644 index 00000000..ab14c7b5 --- /dev/null +++ b/docs/11-upgrading/02-archive/07-streaming-endpoints.md @@ -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 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. + +::: From a6532500d978736a61f75809b5d185826a7681ef Mon Sep 17 00:00:00 2001 From: developerjamiu Date: Mon, 6 Jul 2026 16:47:09 +0100 Subject: [PATCH 2/2] docs: Address PR 673 review (noun-first descriptions, complete exception list, link prefixes) --- .../01-working-with-endpoints/01-working-with-endpoints.md | 2 +- .../01-working-with-endpoints/02-endpoint-inheritance.md | 2 +- docs/06-concepts/01-working-with-endpoints/04-middleware.md | 2 +- docs/06-concepts/04-exceptions.md | 2 +- docs/06-concepts/15-streams.md | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md b/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md index 1513d325..6b345fa8 100644 --- a/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md +++ b/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md @@ -1,7 +1,7 @@ --- slug: /concepts/working-with-endpoints sidebar_label: Working with endpoints -description: Define server methods as endpoints, generate a typed client, and call them from your Flutter app, including pointing the client at each environment. +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 diff --git a/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md b/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md index 6621193e..9454b3fe 100644 --- a/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md +++ b/docs/06-concepts/01-working-with-endpoints/02-endpoint-inheritance.md @@ -1,5 +1,5 @@ --- -description: Base endpoints on other endpoints with inheritance, override behavior from Serverpod modules, and control which subclasses generate client code. +description: Endpoint inheritance lets one endpoint extend another, override behavior from Serverpod modules, and control which subclasses generate client code. --- # Endpoint inheritance diff --git a/docs/06-concepts/01-working-with-endpoints/04-middleware.md b/docs/06-concepts/01-working-with-endpoints/04-middleware.md index 590acaca..c6dfcc77 100644 --- a/docs/06-concepts/01-working-with-endpoints/04-middleware.md +++ b/docs/06-concepts/01-working-with-endpoints/04-middleware.md @@ -1,6 +1,6 @@ --- sidebar_label: Endpoint middleware -description: Add middleware to the API server to intercept endpoint requests and responses for concerns such as logging, caching, and rate limiting. +description: Middleware on the API server intercepts endpoint requests and responses for concerns such as logging, caching, and rate limiting. --- # Endpoint middleware diff --git a/docs/06-concepts/04-exceptions.md b/docs/06-concepts/04-exceptions.md index 0bb8f333..e807637c 100644 --- a/docs/06-concepts/04-exceptions.md +++ b/docs/06-concepts/04-exceptions.md @@ -122,7 +122,7 @@ fields: 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: `ServerpodClientUnauthorized` (401), `ServerpodClientForbidden` (403), `ServerpodClientNotFound` (404), and `ServerpodClientInternalServerError` (500). +- 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: diff --git a/docs/06-concepts/15-streams.md b/docs/06-concepts/15-streams.md index 1fce696f..2fba7c8b 100644 --- a/docs/06-concepts/15-streams.md +++ b/docs/06-concepts/15-streams.md @@ -130,7 +130,7 @@ Read more about serializable exceptions here: [Serializable exceptions](exceptio ## Example: live updates for a filtered query -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. +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 class ChatEndpoint extends Endpoint { @@ -143,7 +143,7 @@ class ChatEndpoint extends Endpoint { Future postToRoom(Session session, int roomId, String text) async { var message = ChatMessage(roomId: roomId, text: text); await ChatMessage.db.insertRow(session, message); - session.messages.postMessage('room_$roomId', message); + await session.messages.postMessage('room_$roomId', message); } } ``` @@ -159,7 +159,7 @@ messages.listen((message) { await client.chat.postToRoom(roomId, 'Hello, room!'); ``` -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)). +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)). ## Streaming endpoints (deprecated)