mates-fullstack
v1.0.7
Published
Full-stack Node.js framework built on the Mates SPA framework. RPC server functions, WebSockets, file-system routing.
Maintainers
Readme
mates-fullstack
TypeScript-first full-stack framework for Mates apps. It adds direct RPC-style server functions, raw HTTP escape hatches, WebSockets, static assets, and production builds without requiring a compiler in production.
No JSX. No virtual DOM. No Express/Hono/Fastify. Runtime server is pure Node.js (node:http). Bundling and TypeScript transforms use esbuild.
Install
npm install mates-fullstack matesAdd scripts to your app:
{
"scripts": {
"dev": "mates-fullstack dev",
"build": "mates-fullstack build",
"start": "mates-fullstack start"
}
}Project structure
mates-fullstack is convention-based; no config file is required.
my-app/
client/
App.ts # root SPA component and client router
client.ts # browser entry point; calls renderX(App, ...)
server/
api/ # RPC functions callable from client imports
helpers/ # server-only utilities; never bundled for browser
main.ts # optional middleware registration
socket/ # optional WebSocket handlers
shared/ # code/types used by both sides
public/ # inside client/ — static files copied to dist/public in production
package.jsonMinimal app
client/App.ts
import { html } from "mates";
export default () => {
return () => html`<h1>Hello from mates-fullstack</h1>`;
};client/client.ts
import { renderX } from "mates";
import App from "./App";
renderX(App, document.getElementById("app")!);server/api/todos.ts
import type { ServerCtx } from "mates-fullstack";
import { ValidationError } from "mates-fullstack";
export async function listTodos(_payload: {}, _ctx: ServerCtx) {
return [{ id: 1, title: "Write docs" }];
}
export async function addTodo(payload: { title: string }, _ctx: ServerCtx) {
if (!payload.title.trim()) {
throw new ValidationError("Invalid todo", {
fields: { title: "Required" },
});
}
return { id: Date.now(), title: payload.title };
}Client code calls server functions directly
import { asyncAction, html } from "mates";
import { listTodos } from "../server/api/todos";
const TodoPage = () => {
const load = asyncAction(() => listTodos({}));
load();
return () => html`
<pre>${JSON.stringify(load.data(), null, 2)}</pre>
`;
};During development and production builds, imports from server/api/** are converted into browser-safe RPC stubs. Imports from any other server/** path are blocked from client code.
Commands
npm run dev # native ESM dev server, no browser bundle
npm run build # production client bundle + compiled server runtime
npm run start # start production server from dist/Development mode
Development intentionally serves source modules instead of a client bundle:
- app source modules:
/_src/** - package modules:
/_pkg/** - framework helpers:
/_mates/** - import map injected into the HTML shell
- chokidar watches
client/(includingclient/public/),server/,shared/,.env*, and config files - reload generation is owned by the parent dev process and delivered through SSE
This makes stack traces point at source-like module URLs and keeps rebuilds fast.
Authentication
Email/Password + Social Login
mates-fullstack provides a complete auth system out of the box. Access tokens are short-lived (15 min default), stored in httpOnly cookies. Refresh tokens (30 days) live in a separate httpOnly cookie and rotate automatically, with JTI replay detection.
// server/main.ts
import { matesAuth, auth, useArctic } from "mates-fullstack";
matesAuth({ secret: process.env.AUTH_JWT_SECRET! });
useArctic({
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
onSuccess: async (profile, ctx) => {
const user = await db.users.upsert({ ... });
await auth.login(ctx, { userId: user.id, email: user.email });
},
},
});Routes /auth/google and /auth/google/callback are registered automatically. onSuccess gives you full control — call auth.login() to issue tokens, or skip it to just link accounts.
10 built-in OAuth providers: Google, GitHub, Discord, Microsoft, Twitter, LinkedIn, Facebook, Apple, Spotify, GitLab. Custom providers via arcticProvider({ handler, ... }).
Cross-Domain SSO
For apps on different domains sharing one auth server. The auth server signs a 30-second code JWT with a shared secret — each app verifies it locally, no round-trip needed.
// auth.com/server/main.ts
import { useSsoProvider } from "mates-fullstack";
useSsoProvider({
secret: process.env.SSO_SECRET!,
login: "/login",
allowedOrigins: ["https://app1.com", "https://app2.com"],
});
// app1.com/server/main.ts
import { useSsoClient } from "mates-fullstack";
useSsoClient({
authUrl: "https://auth.com",
secret: process.env.SSO_SECRET!,
protected: ["/dashboard", "/settings"],
});Each app issues its own httpOnly session cookies. Works with social login — put useArctic() on the auth server, useSsoClient() on every app.
Middleware
All middleware registers globally in server/main.ts and runs in registration order.
Recommended server/main.ts:
import {
matesAuth, requestLogger, useSecurityHeaders, useCors, rateLimit,
} from "mates-fullstack";
matesAuth({ secret: process.env.AUTH_JWT_SECRET! });
requestLogger({ skip: [/^\/health$/, /^\/_mates\//] });
useSecurityHeaders();
useCors({ origins: "https://app.example.com" });
rateLimit({ max: 10, window: "15m", pathPrefix: "/auth" });
rateLimit({ max: 300, window: "1m" });Request Logger
requestLogger({ skip: [/^\/health$/, /^\/assets\//] });Logs every request (method, path, IP) on arrival and every RPC response (status, ms) on completion. Stamps x-request-id on every response. Structured logging — NDJSON in production, pretty-printed in dev.
Security Headers
useSecurityHeaders();
useSecurityHeaders({ contentSecurityPolicy: "default-src 'self'", xFrameOptions: "DENY" });Sets 14 standard security headers on every response — complete helmet-equivalent coverage. Every header individually overridable or disableable. HSTS auto-enabled in production, skipped in dev. CSP and Permissions-Policy are opt-in.
CORS
useCors({ origins: "https://app.example.com", credentials: true });
useCors({ origins: (origin) => origin.endsWith(".example.com") });Handles preflight OPTIONS automatically (returns 204). Reflects origin when credentials: true as required by the CORS spec. Sets Vary: Origin.
Rate Limiting
rateLimit({ max: 100, window: "1m" }); // all routes
rateLimit({ max: 10, window: "15m", pathPrefix: "/auth" }); // auth only
rateLimit({ max: 5, window: "1h", key: (_req, ctx) => String(ctx.auth.userId) }); // per-userSliding-window counter, in-memory. Returns 429 with Retry-After and X-RateLimit-* headers. Auto-evicts expired entries. Custom key function for per-user or per-endpoint limiting.
Raw HTTP hooks
For cases where an external service dictates the HTTP contract (webhooks, custom OAuth flows):
import { onHttpRequest } from "mates-fullstack";
onHttpRequest(async (req, ctx) => {
const url = new URL(req.url);
if (req.method === "POST" && url.pathname === "/stripe-webhook") {
const body = await req.text();
await handleWebhook(body, ctx.reqHeaders["stripe-signature"]);
return new Response("ok");
}
});Request Limits / Resource Protection
Built-in limits prevent CPU and memory exhaustion. All have safe defaults — override only to tune for your app.
// server/main.ts — at the top, before middleware
import { configureRpcRunner } from "mates-fullstack/internal";
configureRpcRunner({
maxJsonBytes: 5 * 1024 * 1024, // default 1MB
maxJsonDepth: 30, // default 50
maxArrayLength: 5000, // default 10_000
maxStringLength: 50_000, // default 100_000
maxUploadBytes: 50 * 1024 * 1024, // default 10MB
maxMultipartParts: 100, // default 200
});Or set server-level limits when starting:
await startServer({
maxConnections: 2000, // default 1000
staticStreamTimeout: 120_000, // default 60s
maxSseClients: 1000, // default 500
});All defaults are active without any configuration call. See DOCS.md for full details.
Public entrypoints
| Import | Use for |
|---|---|
| mates-fullstack | stable app API: errors, redirects, middleware registration, ServerCtx, validation/sanitize/upload helpers |
| mates-fullstack/client | browser hydration and RPC helpers |
| mates-fullstack/browser | browser-safe error/redirect/stream/download helpers |
| mates-fullstack/server | server-safe helpers and middleware runners |
| mates-fullstack/internal | unstable build/runtime/scanner internals for tooling |
License
MIT
