Server
The server is used to expose the Router to the world, it creates both the HTTP and WebSocket handlers. It also handles persistence.
Creating a server
You can create a server using the server function, which takes a schema, router and a storage:
import { server, SQLStorage } from "@live-state/sync/server";
import { schema } from "./schema";
import { appRouter } from "./router";
import { Pool } from "pg";
const lsServer = server({
router: appRouter,
storage: new SQLStorage(
new Pool({
connectionString: "postgresql://admin:admin@localhost:5442/live-state",
})
),
schema,
contextProvider: async ({ transport, headers, cookies, query }) => {
// Generate context from request data
const token = headers.authorization?.replace("Bearer ", "");
const user = token ? await validateToken(token) : null;
return {
userId: user?.id,
user,
transport,
requestId: generateId(),
};
},
middlewares: [
// Global middleware for all routes
async ({ req, next }) => {
console.log(`${req.type} ${req.resource}`);
return next(req);
},
],
});Context providers
Context providers generate request context from incoming HTTP headers, cookies, and query parameters. This context is then available to all middlewares, authorization handlers, and custom mutations.
Basic context provider
const lsServer = server({
router: appRouter,
storage,
schema,
contextProvider: async ({ transport, headers, cookies, query }) => {
return {
transport, // "HTTP" or "WEBSOCKET"
userAgent: headers["user-agent"],
sessionId: cookies.sessionId,
apiKey: query.apiKey,
};
},
});Server middlewares
Server-level middlewares run before route-specific middlewares and apply to all routes.
const loggingMiddleware = async ({ req, next }) => {
const start = Date.now();
console.log(`→ ${req.type} ${req.resource}`, {
context: req.context,
input: req.input,
});
try {
const result = await next(req);
const duration = Date.now() - start;
console.log(`← ${req.type} ${req.resource} (${duration}ms)`);
return result;
} catch (error) {
const duration = Date.now() - start;
console.error(`✗ ${req.type} ${req.resource} (${duration}ms)`, error);
throw error;
}
};
const lsServer = server({
router: appRouter,
storage,
schema,
middlewares: [loggingMiddleware],
});Adapters
The server can be served using different adapters. As of right now, the only adapter is the Express adapter.
Express
The Express adapter requires JSON and query string parsing. And as it's also a WebSocket server, it requires the use of express-ws.
import express from "express";
import expressWs from "express-ws";
import { expressAdapter } from "@live-state/sync/server";
import { lsServer } from "./server";
import cors from "cors";
const { app } = expressWs(express());
app
.use(express.urlencoded({ extended: true }))
.use(express.json())
.use(cors());
expressAdapter(app, lsServer);
app.listen(5001, () => console.log("api running on 5001"));