Getting Started
Get up and running with fossyl in minutes.
Installation
Section titled “Installation”Install fossyl using your preferred package manager:
npm install fossylpnpm add fossylyarn add fossylBasic Usage
Section titled “Basic Usage”Create your first route with fossyl:
// Hover any identifier to see its typeimport { 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" }; });Type Safety
Section titled “Type Safety”fossyl provides full type inference for your routes:
// Hover any identifier to see its typeimport { 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 }; });Adding Validation
Section titled “Adding Validation”Use any validation library you prefer:
// Hover any identifier to see its typeimport { 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" } }; });Query Parameters
Section titled “Query Parameters”Add type-safe query parameter validation:
// Hover any identifier to see its typeimport { 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 }; });Authentication
Section titled “Authentication”Add type-safe authentication to your routes:
// Hover any identifier to see its typeimport { 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}` }; });Next Steps
Section titled “Next Steps”- Explore the API Reference for detailed documentation
- Check out the GitHub repository for examples
- Join the community and contribute!