A minimal, production-oriented service gateway for SvelteKit.
This library provides a structured way to expose backend services through versioned gateway routes while keeping services modular, testable, and HMR-safe.
SvelteKit routes are powerful, but for larger backends you often want:
/api/v1/services/...)This project solves that without introducing a full framework.
/api/v1/services/<service>/<pathβ¦>)βββ src
β βββ lib
β β βββ client # Client-side service caller
β β βββ server
β β βββ helpers
β βββ routes
β βββ api
β βββ v1
β βββ services
β βββ [service_name]
β βββ [...catch]
β βββ +server.ts
βββ static
βββ tests
βββ services
npm i @sourceregistry/sveltekit-service-manager
In this repository you may see
$lib/server/index.js. In production always import from the package.
src/routes/api/v1/services/[service_name]/[...catch]/+server.ts
import { ServiceManager } from '@sourceregistry/sveltekit-service-manager';
const { endpoint, access } = ServiceManager.Base(undefined, {
accessKey: 'api:v1'
});
export const { GET, POST, PUT, DELETE, PATCH, HEAD } = endpoint;
// Allow only selected services through this gateway
access('ping', 'users');
This exposes:
/api/v1/services/ping/*
/api/v1/services/users/*
Each gateway gets its own allowlist, isolated even across HMR:
// Public API
ServiceManager.Base(undefined, { accessKey: 'public' }).access('ping');
// Internal API
ServiceManager.Base(undefined, { accessKey: 'internal' }).access('admin', 'metrics');
import { Router, Action, ServiceManager } from '@sourceregistry/sveltekit-service-manager';
const router = Router()
.GET('/health', () => Action.success(200, { ok: true }))
.GET('/echo/[msg]', ({ params }) =>
Action.success(200, { msg: params.msg })
);
export const service = {
name: 'ping',
route: router
};
export default ServiceManager
.Load(service, import.meta)
.finally(() => console.log('[Service]', `[${service.name}]`, 'Loaded'));
Accessible via:
/api/v1/services/ping/health
/api/v1/services/ping/echo/hello
When loading a service with:
ServiceManager.Load(service, import.meta)
The following happens automatically during Vite HMR:
cleanup() is called (if defined)This prevents:
Compose guards and pass combined state to handlers:
import { middleware, Action } from '@sourceregistry/sveltekit-service-manager';
const requireAuth = async ({ cookies }) => {
const token = cookies.get('token');
if (!token) throw Action.error(401, { message: 'Unauthorized' } as any);
return { token };
};
export const service = {
name: 'users',
route: middleware(
async ({ guard }) => Action.success(200, { token: guard.token }),
requireAuth
)
};
If a service defines local, you can call it directly:
import { Service } from '@sourceregistry/sveltekit-service-manager';
const value = Service('ping');
This is fully typed via App.Services.
The client helper provides a typed, ergonomic way to call public services.
import { Service } from '$lib/client';
const ping = Service('ping');
const result = await ping.call('/health');
ping.route('/health'); // "/api/v1/services/ping/health"
await ping.call('/echo', { message: 'hello' });
Errors throw a ServiceError:
try {
await ping.call('/fail');
} catch (e) {
if (e instanceof ServiceError) {
console.error(e.code); // HTTP status
console.error(e.data); // parsed JSON or text
}
}
Service('ping', {
entryPoint: '/api/v1/services',
executor: fetch
});
Supports dynamic [param] resolution using Page.params.
You can run Express (or similar) inside a service:
import express from 'express';
import { Proxy } from '@sourceregistry/sveltekit-service-manager';
const app = express();
app.get('/hello', (_req, res) => res.json({ hello: 'world' }));
const proxy = new Proxy(app);
export const service = {
name: 'express-demo',
route: (event) => proxy.handle(event)
};
ServiceManagerServiceRouter / RouterService (internal call)ActionmiddlewareServer (WebHTTPServer)Proxy (WebProxyServer)json, text, html, file, fail, errorServiceServiceErrorPublicServicesnpm test
Tests live in tests/services.
Apache 2.0 see LICENSE