JStack Documentation

repository·main·Indexed 25 days ago

https://github.com/upstash/jstack

A development stack for building fast, lightweight, and end-to-end typesafe Next.js 15 applications. JStack leverages Hono, Tailwind CSS, and Drizzle ORM to provide a type-safe RPC client, a router factory for API endpoints, and a CLI for project scaffolding. Key features include support for WebSocket connections, event-based communication via Redis using the IO class, and a flexible middleware system for dependency injection.

Tokens
17.4K
Snippets
65
Records
93
Agent score
84%

What's inside jstack

  1. Overview of JStack

    main

    JStack is a framework designed to build fast, lightweight, and end-to-end typesafe Next.js applications. It is built using a modern stack consisting of:

    • Next.js 15
    • Hono
    • Tailwind CSS
    • Drizzle ORM

    For full features and detailed documentation, visit the official website at https://jstack.app/.

  2. Overview of the JStack technology stack

    main

    JStack is a TypeScript and Next.js stack designed for developer experience and application performance. It integrates the following core technologies:

    • Hono: Used as a portable, lightweight Next.js backend.
    • Zod: Used for runtime validation.
    • Drizzle ORM: Used for database interactions.

    This combination provides end-to-end type safety and supports various deployment environments.

  3. Understand JStack Procedures

    main

    A procedure in JStack is an API endpoint that handles a specific operation. There are three types of procedures:

    • get procedures: For GET requests (reading data).
    • post procedures: For POST requests (modifying, creating, or deleting data).
    • ws (WebSocket) procedures: For real-time, bi-directional communication.

    Recommended file structure for managing procedures:

    app/
      └── server/
          ├── jstack.ts        # Initialize JStack
          ├── index.ts         # Main appRouter
          └── routers/         # Router directory
              ├── user-router.ts
              ├── post-router.ts
              └── payment-router.ts
  4. Configure the JStack client for Cloudflare Worker local development

    main

    When running the backend via wrangler dev on port 8080, update your createClient configuration to point to the local backend URL using the baseUrl option.

    import type { AppRouter } from "@/server"
    import { createClient } from "jstack"
    
    export const client = createClient<AppRouter>({
      // 👇 Add our port 8080 cloudflare URL
      baseUrl: "http://localhost:8080/api",
    })
  5. Create a Router in JStack

    main

    A router is a collection of procedures (API endpoints) related to a specific feature or resource. To create a router, create a new file in server/routers and use j.router() to define it. You can then add procedures using publicProcedure with HTTP methods like .get() or .post().

    import { j, publicProcedure } from "../jstack"
    
    export const postRouter = j.router({
      list: publicProcedure.get(({ c }) => {
        return c.json({ posts: [] })
      }),
    
      create: publicProcedure.post(({ c }) => {
        return c.json({ success: true })
      }),
    })
  6. Set up local development for Cloudflare Workers

    main

    When developing for Cloudflare Workers, you must run the frontend and backend in separate terminal windows:

    1. Frontend: Run npm run dev to start the Next.js application on http://localhost:3000.
    2. Backend: Run wrangler dev to start the Cloudflare backend on http://localhost:8080.

    To ensure the frontend communicates with the local backend, you must configure the baseUrl in your client setup.

    # Terminal 1: Frontend
    npm run dev
    
    # Terminal 2: Backend
    wrangler dev
  7. Set up WebSockets for local development

    main

    JStack uses Upstash Redis as the real-time engine. To develop locally:

    1. Create a Redis database in Upstash.
    2. Copy UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN into a .dev.vars file in your project root:
      UPSTASH_REDIS_REST_URL=
      UPSTASH_REDIS_REST_TOKEN=
    3. Start your Cloudflare backend using wrangler dev.
    4. Configure your client baseUrl to point to the local Cloudflare Worker (usually http://localhost:8080/api).
    import type { AppRouter } from "@/server"
    import { createClient } from "jstack"
    
    export const client = createClient<AppRouter>({
      baseUrl: "http://localhost:8080/api",
    })
  8. Use JStack for state-agnostic type-safe API calls

    main

    Unlike tRPC, which couples type-safety to React Query hooks, JStack provides a type-safe client that acts as a fetch wrapper. This allows you to use it with any state management library or pattern, including:

    • React Query: Use standard React Query best practices without tRPC-specific abstractions.
    • Standalone State Managers: Use the client directly inside stores like Zustand, Jotai, or Redux outside of the React component scope.

    Because the client is just a type-safe fetch wrapper, it does not associate frontend declarations with specific backend procedures in a way that limits your state management choices.

  9. Configure environment variables for Cloudflare Workers

    main

    Cloudflare Workers use a specific environment variable system.

    Local development: Use a .dev.vars file for local development (e.g., with wrangler dev):

    DATABASE_URL=your-database-url

    Accessing variables in the Frontend: In client and server components, you can use standard syntax:

    const DATABASE_URL = process.env.DATABASE_URL

    Accessing variables in the Backend (API): In the backend, you must use the env adapter from hono/adapter to access variables from the context c:

    import { env } from "hono/adapter"
    import { j } from "jstack"
    
    export const postRouter = j.router({
      recent: j.procedure.get(({ c }) => {
        const { DATABASE_URL } = env(c)
      }),
    })
    // Backend (API)
    import { env } from "hono/adapter"
    import { j } from "jstack"
    
    export const postRouter = j.router({
      recent: j.procedure.get(({ c }) => {
        const { DATABASE_URL } = env(c)
      }),
    })