Server-Sent Events (SSE) API
Relevant source files
The following files were used as context for generating this wiki page:
The Server-Sent Events (SSE) API provides a mechanism for the server to push real-time data to the client over a single, long-lived HTTP connection. This feature is built using the oRPC framework, with a clear separation between the API contract definition and its server-side implementation. The contract, defined in the @contract-based-monorepo/contract package, specifies the data structures for the stream, while the api application provides the concrete logic for handling requests and streaming data.
This one-way communication channel is ideal for scenarios like live notifications, status updates, or data feeds where the server initiates the communication after an initial client connection. The implementation demonstrates a simple time-based data push, but the architecture supports more complex, event-driven use cases.
Architecture Overview
The SSE functionality is integrated into the project's oRPC system. It consists of two main parts: the contract and the handler.
- Contract (
@contract-based-monorepo/contract): Defines thesseprocedure, its inputs (query), and the shape of the data packets (payload). - Handler (
apps/api): Implements the server-side logic for thesseprocedure, handling the client connection and streaming data back.
The Hono web server exposes this oRPC procedure through a generic RPC handler.
The following diagram illustrates the high-level component interaction.
Contract Definition
The SSE API contract is defined in the packages/contract workspace. It uses oRPC's o.stream() utility to specify a streaming endpoint and Zod for schema validation.
Sources: packages/contract/src/contract/sse.ts
Procedure Specification
The sse procedure is defined as a public stream.
| Property | Type / Value | Description |
|---|---|---|
| Type | stream | Defines this as a streaming procedure, suitable for SSE or WebSockets. |
| Access | isPublic: true | The endpoint does not require authentication. |
| Query | z.object({ name: z.string() }) | The client must provide a name as a string in the query parameters of the request. |
| Payload | z.object({ message: z.string() }) | Each event sent from the server to the client will be an object with a message string. |
| Result | z.void() | The procedure does not return a single, final result upon completion, as it is a continuous stream. |
Sources: packages/contract/src/contract/sse.ts:4-11
Code Definition
// packages/contract/src/contract/sse.ts
import { o } from "@orpc/contract";
import { z } from "zod";
export const sse = o.stream({
isPublic: true,
query: z.object({
name: z.string(),
}),
payload: z.object({
message: z.string(),
}),
result: z.void(),
});
Sources: packages/contract/src/contract/sse.ts:1-13
This contract is then exported as part of the main application contract, making it available to the oRPC server and for client-side type generation.
Sources: packages/contract/src/contract/index.ts:4, 15
Server Implementation
The server-side logic for the SSE stream is implemented in the api application. It uses a public oRPC procedure handler (pub) and leverages Hono's streaming helpers to manage the SSE connection.
Sources: apps/api/src/router/sse.ts
Handler Logic
The handler performs the following steps:
- It is defined as a handler for the public
sseprocedure. - It receives the validated
input(from the query) and the Hono contextc. - It uses
sse(c, ...)to initialize the Server-Sent Events stream. - An initial "hello" message is sent to the client, incorporating the
namefrom the input. - A
forloop runs 5 times with a 1-second delay between iterations. - In each iteration, a new message containing the current timestamp is written to the stream.
- After the loop completes, the
stream.close()method is called to terminate the connection.
Data Flow Sequence
This sequence diagram shows the interaction between a client and the server for a typical SSE session.
Sources: apps/api/src/router/sse.ts:5-18
Implementation Snippet
// apps/api/src/router/sse.ts
import { sse } from "hono/streaming";
import { pub } from "../orpc";
export const sseHandler = pub.sse.handler(async ({ input, c }) => {
return sse(c, async (stream) => {
// 1. Send a hello message
await stream.writeSSE({
data: JSON.stringify({ message: `Hello ${input.name}` }),
});
// 2. Send a message every second
for (let i = 0; i < 5; i++) {
await stream.sleep(1000);
const message = `It is ${new Date().toLocaleTimeString()}`;
await stream.writeSSE({
data: JSON.stringify({ message }),
});
}
// 3. Close the stream
stream.close();
});
});
Sources: apps/api/src/router/sse.ts:1-22
API Integration and Exposure
The SSE feature is integrated into the main application through standard routing and contract composition.
RPC Routing
The main Hono application in apps/api/src/index.ts directs all requests matching the /rpc/* path to handleRPCRequests. This oRPC handler is responsible for mapping incoming requests to their corresponding procedure implementations, including the sseHandler.
Sources: apps/api/src/index.ts:11
OpenAPI Documentation
The sse procedure is automatically included in the project's OpenAPI specification. The generator in apps/api/src/handlers/openapi.ts processes the entire contract object, ensuring that the SSE endpoint and its schemas are part of the generated API documentation available at /api/docs.
Sources: apps/api/src/handlers/openapi.ts:8, 10
Summary
The Server-Sent Events API is a well-defined and cleanly implemented feature that leverages the project's contract-first oRPC architecture. By defining the stream's shape in a shared contract package and implementing the logic in the api backend, the system maintains strong type safety and a clear separation of concerns. The use of Hono's streaming utilities simplifies the server-side implementation, allowing the developer to focus on the business logic of the data being streamed.