JSandy makes it easy to build performant, reliable Next.js applications. You can start a new project in one of two ways:
Using the CLI (recommended)
Manual Setup
Especially if you use the CLI (which does all the setup automatically), this guide will help you understand key components of your new (and hopefully favorite 🤞) Next.js & TypeScript tech stack.
Quickstart
The quickest way to get started is with the CLI:
Terminal
npx create-jsandy-app@latest
npx create-jsandy-app@latest
That's it! 🎉 The CLI creates a brand-new project following best practices and modern Next.js patterns. I recommend reading on to understand each part of JSandy.
1. File Structure
Let's explore the main building blocks of JSandy. I recommend the following file structure for your Next.js application:
This file is the point where we initialize JSandy. Optionally, you can specify your environment variable type here, which makes it type-safe across your application.
server/jsandy.ts
import { jsandy } from "@jsandy/rpc"interface Env { Bindings: { DATABASE_URL: string }}export const j = jsandy.init<Env>()/** * Public (unauthenticated) procedures * This is the base part you use to create new procedures. */export const publicProcedure = j.procedure
import { jsandy } from "@jsandy/rpc"interface Env { Bindings: { DATABASE_URL: string }}export const j = jsandy.init<Env>()/** * Public (unauthenticated) procedures * This is the base part you use to create new procedures. */export const publicProcedure = j.procedure
The core of JSandy is the appRouter - it's the entry point to your Next.js backend and manages all your API routes:
app/server/index.ts
import { j } from "./jsandy"/** * This is your base API. * Here, you can handle errors, not-found responses, cors and more. */const api = j .router() .basePath("/api") .use(j.defaults.cors) .onError(j.defaults.errorHandler)/** * This is the main router for your server. * All routers in /server/routers should be added here manually. */const appRouter = j.mergeRouters(api, { // ...})export type AppRouter = typeof appRouterexport default appRouter
import { j } from "./jsandy"/** * This is your base API. * Here, you can handle errors, not-found responses, cors and more. */const api = j .router() .basePath("/api") .use(j.defaults.cors) .onError(j.defaults.errorHandler)/** * This is the main router for your server. * All routers in /server/routers should be added here manually. */const appRouter = j.mergeRouters(api, { // ...})export type AppRouter = typeof appRouterexport default appRouter
When a new HTTP request comes in to your server, the appRouter knows where to route the request.
The function gets a context object (c) with helpful utilities
We return a mocked post using c.json()
5. Connecting a Router
Let's connect our new router to make it available for API requests:
server/index.ts
import { j } from "./jsandy"import { postRouter } from "./routers/post-router"const api = j .router() .basePath("/api") .use(j.defaults.cors) .onError(j.defaults.errorHandler)const appRouter = j.mergeRouters(api, { post: postRouter,})export type AppRouter = typeof appRouterexport default appRouter
import { j } from "./jsandy"import { postRouter } from "./routers/post-router"const api = j .router() .basePath("/api") .use(j.defaults.cors) .onError(j.defaults.errorHandler)const appRouter = j.mergeRouters(api, { post: postRouter,})export type AppRouter = typeof appRouterexport default appRouter
6. API Setup
To handle all incoming requests with our appRouter, create a catch-all API route:
app/api/[[...route]]/route.ts
import appRouter from "@/server"import { handle } from "hono/vercel"export const GET = handle(appRouter.handler)export const POST = handle(appRouter.handler)
import appRouter from "@/server"import { handle } from "hono/vercel"export const GET = handle(appRouter.handler)export const POST = handle(appRouter.handler)
That's it! 🎉
You can now send API requests to http://localhost:3000/api/post/recent. This path is made up of three different parts: