Installation

Install the Package

Let's start by adding Better Query to your project:

npm install better-query

If you're using a separate client and server setup, make sure to install Better Query in both parts of your project.

Database Dependencies

Install the appropriate database driver for your provider:

# For SQLite
npm install better-sqlite3

# For PostgreSQL  
npm install pg @types/pg

# For MySQL
npm install mysql2

Create A Better Query Instance

Create a file named query.ts in one of these locations:

  • Project root
  • lib/ folder
  • utils/ folder

You can also nest any of these folders under src/, app/ or server/ folder. (e.g. src/lib/query.ts, app/lib/query.ts).

And in this file, import Better Query and create your query instance. Make sure to export the query instance with the variable name query or as a default export.

query.ts
import { betterQuery } from "better-query";

export const query = betterQuery({
  //...
});

Configure Database

Better Query requires a database to store your data. You can easily configure Better Query to use SQLite, PostgreSQL, or MySQL, and more!

query.ts
import { betterQuery } from "better-query";
import Database from "better-sqlite3";

export const query = betterQuery({
    database: new Database("./sqlite.db"),
})
query.ts
import { betterQuery } from "better-query";
import { Pool } from "pg";

export const query = betterQuery({
    database: new Pool({
        // connection options
    }),
})
query.ts
import { betterQuery } from "better-query";
import { createPool } from "mysql2/promise";

export const query = betterQuery({
    database: createPool({
        // connection options
    }),
})

Alternatively, if you prefer to use an ORM, you can use one of the built-in adapters.

query.ts
import { betterQuery } from "better-query";
import { drizzleAdapter } from "better-query/adapters/drizzle";
import { db } from "@/db"; // your drizzle instance

export const query = betterQuery({
    database: drizzleAdapter(db, {
        provider: "pg", // or "mysql", "sqlite"
    }),
});
query.ts
import { betterQuery } from "better-query";
import { prismaAdapter } from "better-query/adapters/prisma";
// If your Prisma file is located elsewhere, you can change the path
import { PrismaClient } from "@/generated/prisma";

const prisma = new PrismaClient();
export const query = betterQuery({
    database: prismaAdapter(prisma, {
        provider: "sqlite", // or "mysql", "postgresql", ...etc
    }),
});

If your database is not listed above, check out our other supported databases for more information, or use one of the supported ORMs.

Create Database Tables

Better Query includes a CLI tool to help manage the schema required by the library.

  • Generate: This command generates an ORM schema or SQL migration file.

If you're using Kysely, you can apply the migration directly with migrate command below. Use generate only if you plan to apply the migration manually.

Terminal
npx better-query generate
  • Migrate: This command creates the required tables directly in the database. (Available only for the built-in Kysely adapter)
Terminal
npx better-query migrate

see the CLI documentation for more information.

If you instead want to create the schema manually, you can find the core schema required in the database section.

Define Resources

Configure the resources (data models) you want to create CRUD endpoints for. Better Query uses Zod schemas for validation and type safety.

query.ts
import { betterQuery, createResource, withId } from "better-query";
import { z } from "zod";

// Define your resource schema
const productSchema = withId({
  name: z.string().min(1, "Product name is required"),
  description: z.string().optional(),
  price: z.number().min(0, "Price must be positive"),
  status: z.enum(["active", "inactive", "draft"]).default("draft"),
});

export const query = betterQuery({
  //...other options
  resources: [ 
    createResource({ 
      name: "product", 
      schema: productSchema, 
      permissions: { 
        create: () => true,         // Allow all creates
        read: () => true,           // Allow all reads
        update: () => true,         // Allow all updates
        delete: () => false,        // Disallow all deletes
        list: () => true,           // Allow all lists
      }, 
    }), 
  ], 
});

You can create even more advanced resource configurations using hooks, plugins, and more advanced permission logic.

Mount Handler

To handle API requests, you need to set up a route handler on your server.

Create a new file or route in your framework's designated catch-all route handler. This route should handle requests for the path /api/* (unless you've configured a different base path).

Better Query supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks.

/app/api/[...query]/route.ts
import { query } from "@/lib/query-client";
import { NextRequest } from "next/server";

const handler = query.handler;

export const POST = async (req: NextRequest) => handler(req);
export const PATCH = async (req: NextRequest) => handler(req);
export const DELETE = async (req: NextRequest) => handler(req);
export const GET = handler;
src/index.ts
import { Hono } from "hono";
import { query } from "./query"; // path to your query file
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";

const app = new Hono();

app.on(["POST", "GET", "PATCH", "DELETE"], "/api/*", (c) => query.handler(c.req.raw));

serve(app);
server.ts
import express from "express";
import cors from "cors";
import { query } from "./query.js";

const app = express();

app.use(cors());
app.use(express.json());

app.all("/api/*", query.handler);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Server running at http://localhost:${PORT}`);
});

This will also work for any other node server framework like express, fastify, hapi, etc., but may require some modifications. Note that CommonJS (cjs) isn't supported.

Create Client Instance

The client-side library helps you interact with the query server. Better Query comes with a client for all the popular web frameworks, including vanilla JavaScript.

  1. Import createQueryClient from the package for your framework (e.g., "better-query/react" for React).
  2. Call the function to create your client.
  3. Pass the base URL of your query server. (If the query server is running on the same domain as your client, you can skip this step.)

If you're using a different base path other than /api make sure to pass the whole URL including the path. (e.g. http://localhost:3000/api/query)

lib/query-client.ts
import { createQueryClient } from "better-query/client"
export const queryClient = createQueryClient({
    /** The base URL of the server (optional if you're using the same domain) */
    baseURL: "http://localhost:3000/api"
})
lib/query-client.ts
import { createQueryClient } from "better-query/react"
export const queryClient = createQueryClient({
    /** The base URL of the server (optional if you're using the same domain) */
    baseURL: "http://localhost:3000/api"
})

Tip: You can also export specific methods if you prefer:

export const { products, users, orders } = createQueryClient<typeof query>()

🎉 That's it!

That's it! You're now ready to use Better Query in your application. Continue to basic usage to learn how to use the query instance to manage your resources.

On this page