I'm using the ts-rest-hono library for this. This is how I summarize the process of creating APIs with ts-rest.
It's built on these concepts:
- π Contract
 - π€ Server
 - π Endpoint Creation
 
If you used GraphQL before, think of this as your "Schema". This is where you define what methods, paths, and responses your API endpoints use. It's a very small file that only defines what your API does. The magic is that your Server(Backend) and your Client(Frontend) agrees on this.
import { initContract } from "@ts-rest/core";
import { z } from "zod";
const c = initContract();
export const TodoSchema = z.object({
  id: z.string(),
  title: z.string(),
  completed: z.boolean(),
});
export const contract = c.router({
  getTodos: {
    method: "GET",
    path: "/todos",
    responses: {
      201: TodoSchema.array(),
    },
    summary: "Create ",
  },
  createTodo: {
    method: "POST",
    path: "/todo",
    responses: {
      201: TodoSchema,
    },
    body: z.object({
      title: z.string(),
      completed: z.boolean(),
    }),
    summary: "Creates a todo.",
  },
});This is kind of like your "resolver" (in GraphQL) for the contract. In TS-REST, you need a package that plays well with the server you choose. The officially supported ones are Nest, Next, and Express. For Hono, we use ts-rest-hono. The function we use to make the server is initServer.
Server is also called your "router". π‘
import { initServer } from "ts-rest-hono";
import { contract } from "./contract";
import { nanoid } from "nanoid"; // optional
const s = initServer();
type Todo = {
  id: string;
  title: string;
  completed: boolean;
};
const todos: Todo[] = [];
const router = s.router(contract, {
  getTodos: async () => {
    return {
      status: 201,
      body: todos,
    };
  },
  createTodo: async ({ body: { completed, title } }) => {
    const newTodo = {
      id: nanoid(),
      title,
      completed,
    };
    todos.push(newTodo);
    return {
      status: 201,
      body: newTodo,
    };
  },
});
export default router;In Hono, you need an creation package that plays well with the server you choose. Again, we use ts-rest-hono. The function we use for endpoint creation is createHonoEndpoints.
Endpoint creation hooks up your contract, server(router), and app(the actual backend server framework). (very confusing I know, it's hard to be consistent with the terminology here)
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { createHonoEndpoints } from "ts-rest-hono";
import { contract } from "./contract";
import router from "./honoRouter";
const app = new Hono();
app.get("/", (c) => {
  return c.text("π₯ Hello Hono!");
});
createHonoEndpoints(contract, router, app);
serve(app, (info) => {
  console.log(`Listening on http://localhost:${info.port}`);
});So TS-REST isn't really "backend agnostic" in the sense that it can work with any backend. It's a yes and no answer:
- "yes" - it can run on any backend. You only need to define a contract.
 - "no" - BUT you need to have a package to handle the server (router) and endpoint creation parts. The officially supported ones are Nest, Next, and Express. For Hono, we use 
ts-rest-hono.