AppRouter Overview
The appRouter in JSandy:
- Routes requests to the procedure that handles them
- Serves as an entry point to deploy your backend
app/
└── server/
├── jsandy.ts # Initialize JSandy
├── index.ts # Main appRouter
└── routers/ # Router directory
├── user-router.ts
├── post-router.ts
└── ...An appRouter knows all the routes and their handlers; deploying your backend is as easy as deploying the appRouter. Since JSandy is built on top of Hono, you can deploy this router anywhere: Vercel, Netlify, Cloudflare, AWS, Railway, etc.
Creating the appRouter
An appRouter is built on top of a base api that defines global behaviors. For example, where your API is served from, error handling, 404 response handling, or global middleware:
import { j } from "./jsandy"
import { postRouter } from "./routers/post-router"
// 1️⃣ Creating the base API with global configuration
const api = j
.router()
.basePath("/api")
.use(j.defaults.cors)
.onError(j.defaults.errorHandler)
// 2️⃣ Merging with feature routers
const appRouter = j.mergeRouters(api, {
post: postRouter,
})
export type AppRouter = typeof appRouter
export default appRouterConfiguration Options
All JSandy routers are lightweight, minimal extensions built on top of the Hono API. Any method you can call on the Hono API, you can also call on JSandy routers:
- Global error handling
- 404 response handling
- View all available methods
Here are the most common methods:
Error Handling
I recommend using JSandy's default error handler via j.defaults.errorHandler. It catches all router errors and returns standardized error responses that you can easily handle on the frontend.
import { j } from "../jsandy"
const api = j
.router()
.basePath("/api")
.use(j.defaults.cors)
.onError(j.defaults.errorHandler)"use client"
import { useMutation } from "@tanstack/react-query"
import { HTTPException } from "hono/http-exception"
import { client } from "@/lib/client"
export default function Page() {
const { mutate: createPost } = useMutation({
mutationFn: async () => {
const res = await client.post.create.$post()
return await res.json()
},
onError: (err: HTTPException) => {
console.log(err.message)
},
})
return <button onClick={() => createPost()}>Create Post</button>
}You can also customize error handling if needed:
api.onError((err, c) => {
console.error(`${err}`)
return c.text("Custom Error Message", 500)
})Base Path
To configure where your API is served from, adjust the basePath parameter and rename your api directory to match this new path:
// 👇 Serve all routes under /api/*
const api = j
.router()
.basePath("/api")
.use(j.defaults.cors)
.onError(j.defaults.errorHandler)
CORS
Nobody likes CORS (🤮), but it's a necessary evil for security reasons. I recommend using JSandy's default CORS middleware:
import { InferRouterInputs, InferRouterOutputs } from "@jsandy/rpc"
import { j } from "./jsandy"
import { postRouter } from "./routers/post-router"
const api = j
.router()
.basePath("/api")
.use(j.defaults.cors)
.onError(j.defaults.errorHandler)You can also customize CORS if needed using Hono's built-in CORS middleware. In this case, it's essential to whitelist the x-is-superjson header, as JStack uses this internally for c.superjson() responses:
import { cors } from "hono/cors"
cors({
allowHeaders: ["x-is-superjson"],
exposeHeaders: ["x-is-superjson"],
origin: (origin) => origin, // default: allow any origin
credentials: true, // default: allow credentials
})Inferring Router Inputs/Outputs
import type { AppRouter } from "./jsandy"
import type { InferRouterInputs, InferRouterOutputs } from "@jsandy/rpc"
type InferInput = InferRouterInputs<AppRouter>
type InferOutput = InferRouterOutputs<AppRouter>
// 👇 Usage: InferInput[<router>][<procedure>]
type Input = InferInput["post"]["example"]
type Output = InferOutput["post"]["example"]