Server

Server

Example

const { handler, clientConfig, openAPIPathsObject } = makeRequestHandler({
  input: z.object({
    name: z.string(),
  }),
  output: z.object({
    greeting: z.string(),
  }),
  run: async ({ request, input, sendOutput }) => {
    const { name } = input;
    return sendOutput({ greeting: `Hello, ${name}!` });
  },
  path: "/api/hello",
  method: "GET",
  description: "Greets the user",
  authenticate: async (req) => {
    return true;
  },
});

Props

NameTypeDescription
inputAnyZodObjectA Zod schema describing the shape of your input. Clover will look for the input inside query params, path params or JSON formatted request body.
outputAnyZodObjectA Zod schema describing the shape of your output. You can use the sendOutput helper inside your run function to ensure that you conform to the output.
run({ request, input, sendOutput }) => Promise<Response>The logic you want to run. You can use the validated input, or use the raw request. You can also send the response any way you like, but the sendOutput helper will help you conform to the output schema.
pathstringThe relative path where your handler can be reached e.g. /api/hello-world
methodHTTPMethodGET, POST, PUT, PATCH or DELETE. This helps Clover generate appropriate documentation, and also helps it figure out where to look for the input. For example, input will be parsed from query and path parameters for GET requests.
description?stringUseful for generating OpenAPI documentation for this route.
authenticate?(request: Request) => Promise<boolean>If you supply this property, it will mark the route as protected with Bearer auth in the documentation. You can verify authentication status any way you like. Return true if the request is authenticated.

Return values

NameTypeDescription
handler(request: Request) => ResponseAn augmented server route handler.
clientConfigIClientConfigA dummy variable used to extract types and pass them to the client. You can read more in the Client docs.
openAPIPathsObjectoas31.PathsObjectA generated OpenAPI schema for this route. You can read more about how to use this in the OpenAPI docs.

OpenAPI

Clover uses openapi3-ts and @anatine/zod-openapi to generate OpenAPI schemas for your routes. Each route returns an oas31.PathsObject. You can stitch together all the schemas into a combined document like below:

// openapi.ts
 
import { OpenAPIObject, OpenAPIPathsObject } from "@sarim.garden/clover";
import { openAPIPathsObject as someRouteOpenAPISchema } from "./some/route";
import { openAPIPathsObject as anotherRouteOpenAPISchema } from "./another/route";
 
const pathsObject: OpenAPIPathsObject = [
  someRouteOpenAPISchema,
  anotherRouteOpenAPISchema,
].reduce((acc, curr) => {
  Object.keys(curr).forEach((k) => {
    acc[k] = {
      ...acc[k],
      ...curr[k],
    };
  });
  return acc;
}, {});
 
export const document: OpenAPIObject = {
  info: {
    title: "My API",
    version: "1.0.0",
  },
  openapi: "3.0.0",
  paths: pathsObject,
};

Usage with frameworks

Next.js

Clover works with standard Web Request and Response APIs, which are only available in the new app directory in Next.js 13.4.

// app/hello/route.ts
 
const { handler } = makeRequestHandler({
  method: "GET",
  // ...
});
 
export { handler as GET };

Swagger UI

Setting up Swagger would vary from framework to framework, but here is an illustrative example for Next.js:

// app/openapi.json/route.ts
 
import { document } from "../../openapi";
import { NextResponse } from "next/server";
 
export const GET = () => {
  return NextResponse.json(document);
};
// app/swagger/page.tsx
 
"use client";
 
import "swagger-ui-react/swagger-ui.css";
import SwaggerUI from "swagger-ui-react";
import { useEffect, useState } from "react";
 
const SwaggerPage = () => {
  const [mounted, setMounted] = useState(false);
 
  useEffect(() => {
    setMounted(true);
  }, []);
 
  if (!mounted) {
    return null;
  }
 
  return <SwaggerUI url="/openapi.json" />;
};
 
export default SwaggerPage;

Input parsing

There are three supported input types: query parameters, path parameters and JSON request bodies. Depending on the HTTP method used, Clover will parse the input from the appropriate source.

MethodInput source
GETPath + query
DELETEPath + query
POSTPath + body
PUTPath + body
PATCHPath + body

Path parameters

Clover uses path-to-regexp (opens in a new tab) to parse path parameters e.g. if you have an input schema z.object({ id: z.string() }) and path /api/users/:id, then Clover will parse the request URL to find the ID and use it to populate the input inside the run() function.