Skip to content
fossyl

Getting Started

Get up and running with fossyl in minutes.

Install fossyl using your preferred package manager:

npm install fossyl

Create your first route with fossyl:

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath> } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
const _userRouteRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/users/:id")
.get<Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route(({ url{ id: string } & { readonly __kind: "url" } }) => async () => {
const userIdstring = url{ id: string } & { readonly __kind: "url" }.idstring;
return { typeName: "User", id: userIdstring, name: "John Doe" };
});

fossyl provides full type inference for your routes:

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath> } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
const _routeRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/posts/:postId/comments/:commentId")
.get<Response extends ResponseData>( handler: ( parameters: { url: { postId: string; commentId: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route(({ url{ postId: string; commentId: string } & { readonly __kind: "url" } }) => async () => {
return { typeName: "Comment", postId: url{ postId: string; commentId: string } & { readonly __kind: "url" }.postIdstring, commentId: url{ postId: string; commentId: string } & { readonly __kind: "url" }.commentIdstring };
});

Use any validation library you prefer:

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath> } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
const _createUserRouteRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/users")
.validator<RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route }((data{}) => {
if (!data{} || typeof data{} !== "object") {
throw new Error("Invalid body");
}
const { namestring, emailstring } = data{} as { namestring?: unknown; emailstring?: unknown };
if (typeof namestring !== "string" || typeof emailstring !== "string") {
throw new Error("Name and email must be strings");
}
return { namestring, emailstring };
})
.post<Response extends ResponseData>( handler: ( body: { name: string; email: string } & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route((body{ name: string; email: string } & { readonly __kind: "body" }) => async () => {
return { typeName: "User", id: "123", ...body{ name: string; email: string } & { readonly __kind: "body" } };
});

Add type-safe query parameter validation:

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath> } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
const _searchRouteRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/search")
.querystring((data) => data as { qstring: string; limitnumber | undefined?: number })
.get<Response extends ResponseData>( handler: ( parameters: { query: { q: string; limit?: number } & { readonly __kind: "query" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route(({ querystring }) => async () => {
return { typeName: "SearchResults", results: [], query: querystring.qstring, limit: querystring.limitnumber | undefined };
});

Add type-safe authentication to your routes:

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath>, authWrapper<T>(auth: T) => T & Authentication } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
const _protectedRouteRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/protected")
.authenticator<Auth extends Authentication>( authenticationFunction: AuthenticationFunction<Auth>, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: undefined extends Auth ? undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: undefined extends Auth ? undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route } } & { get: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route post: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route delete: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route }(async (headersRecord<string, string>) => {
const tokenstring = headersRecord<string, string>["authorization"];
if (!tokenstring) {
throw new Error("Unauthorized");
}
return authWrapper<T>(auth: T) => T & Authentication({ userId: "123", role: "admin" });
})
.get<Response extends ResponseData>( handler: ( auth: { userId: string; role: string } & Authentication & { readonly __kind: "auth" }, ) => () => Promise<Response>, ) => Route((auth{ userId: string; role: string } & Authentication & { readonly __kind: "auth" }) => async () => {
return { typeName: "Message", message: `Hello, user ${auth{ userId: string; role: string } & Authentication & { readonly __kind: "auth" }.userIdstring}` };
});