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:

export const getUserRoute = { 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/users">
.createEndpoint<Path extends `/api/users${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((params{ url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }) => async () => {
const userUserRow = { id: number name: string email: string created_at: string } = await userService.getUserRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean }(Number(params{ url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }.url{ id: string } & { readonly __kind: "url" }.idstring));
return { typeName: "User" as const, ...userUserRow = { id: number name: string email: string created_at: string } };
});

fossyl provides full type inference for your routes:

export const getUserRoute = { 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/users">
.createEndpoint<Path extends `/api/users${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((params{ url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }) => async () => {
const userUserRow = { id: number name: string email: string created_at: string } = await userService.getUserRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean }(Number(params{ url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }.url{ id: string } & { readonly __kind: "url" }.idstring));
return { typeName: "User" as const, ...userUserRow = { id: number name: string email: string created_at: string } };
});

Use any validation library you prefer:

export const createUserRoute = { 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/users">
.createEndpoint<Path extends `/api/users${string}`>( path: Path, ) => Endpoint<Path, true>("/api/users")
.authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>(authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>)
.validator<RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route }(createUserValidator( data: unknown, params?: util.InexactPartial<ParseParams>, ) => { name: string; email: string })
.post<Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => ( body: { name: string; email: string } & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route((_auth{ userId: string } & Authentication & { readonly __kind: "auth" }) => (body{ name: string; email: string } & { readonly __kind: "body" }) => async () => {
const userUserRow = { id: number name: string email: string created_at: string } = await userService.createUserRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean }(body{ name: string; email: string } & { readonly __kind: "body" }.namestring, body{ name: string; email: string } & { readonly __kind: "body" }.emailstring);
return { typeName: "User" as const, ...userUserRow = { id: number name: string email: string created_at: string } };
});

Add type-safe query parameter validation:

export const searchTodosRoute = { 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/todos">
.createEndpoint<Path extends `/api/todos${string}`>( path: Path, ) => Endpoint<Path, true>("/api/todos/search")
.query{ q: string; limit?: number; offset?: number } & { readonly __kind: "query" }((data{ typeName: "Todo"; id: number }[]): { qstring: string; limitnumber | undefined?: number; offsetnumber | undefined?: number } => {
const paramsRecord<string, string | undefined> = data{ typeName: "Todo"; id: number }[] as Record<string, string | undefinedundefined>;
if (!paramsRecord<string, string | undefined>.qstring || paramsRecord<string, string | undefined>.qstring.trim() => string() === "") {
throw new Error('Search query "q" is required');
}
return {
q: paramsRecord<string, string | undefined>.qstring,
limit: paramsRecord<string, string | undefined>.limitnumber | undefined ? Number(paramsRecord<string, string | undefined>.limitnumber | undefined) : undefinedundefined,
offset: paramsRecord<string, string | undefined>.offsetnumber | undefined ? Number(paramsRecord<string, string | undefined>.offsetnumber | undefined) : undefinedundefined,
};
})
.get<Response extends ResponseData>( handler: ( parameters: { query: { q: string; limit?: number; offset?: number } & { readonly __kind: "query" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route(({ query{ q: string; limit?: number; offset?: number } & { readonly __kind: "query" } }) => async () => {
return {
typeName: "SearchResult" as const,
q: query{ q: string; limit?: number; offset?: number } & { readonly __kind: "query" }.qstring,
limit: query{ q: string; limit?: number; offset?: number } & { readonly __kind: "query" }.limitnumber | undefined,
offset: query{ q: string; limit?: number; offset?: number } & { readonly __kind: "query" }.offsetnumber | undefined,
};
});

Add type-safe authentication to your routes:

export const getTodoRoute = { 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/todos">
.createEndpoint<Path extends `/api/todos${string}`>( path: Path, ) => Endpoint<Path, true>("/api/todos/:id")
.authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>(authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>)
.get<Response extends ResponseData>( handler: ( parameters: { query: { q: string; limit?: number; offset?: number } & { readonly __kind: "query" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route((paramsRecord<string, string | undefined>) => (_auth{ userId: string } & Authentication & { readonly __kind: "auth" }) => async () => {
const todoTodoRow = { id: number title: string completed: number created_at: string } = await todoService.getTodoRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean }(Number(paramsRecord<string, string | undefined>.url{ id: string } & { readonly __kind: "url" }.idnumber));
return { typeName: "Todo" as const, ...todoTodoRow = { id: number title: string completed: number created_at: string } };
});