# Quickstart

> Add better-push to an existing Node and Postgres application and verify the first delivery.

Canonical documentation: /docs/quickstart



This guide uses the CLI to add better-push to an existing application. It keeps
provider setup out of the first loop by using the local emulator.

## Prerequisites [#prerequisites]

* Node.js 22 or newer
* PostgreSQL and a `DATABASE_URL`
* A supported framework and database client

The CLI supports Next.js, TanStack Start, Express, Hono, and NestJS with `pg`,
Drizzle, or Prisma. Use a [framework walkthrough](/docs/walkthroughs) if you
prefer to wire the files manually.

## 1. Scaffold the integration [#1-scaffold-the-integration]

Run this from the application root:

```bash
npx @better-push/cli init
```

The command detects your stack, previews its changes, and asks before writing.
Use `--dry-run` to inspect the output without changing files.

It creates or proposes these integration points:

| File                      | Purpose                                                             |
| ------------------------- | ------------------------------------------------------------------- |
| `push.ts`                 | Database, providers, session resolver, and notification definitions |
| Framework route or module | Mounts the better-push HTTP handler at `/api/push`                  |
| Schema artifact           | Defines the `bp_*` tables for `pg`, Drizzle, or Prisma              |
| `public/sw.js`            | Receives Web Push in browser applications                           |
| Environment entries       | Development VAPID values and required placeholders                  |

Existing files are skipped unless you explicitly pass `--force`.

## 2. Connect your application [#2-connect-your-application]

Open the generated `push.ts` and complete every `TODO`:

1. Point the adapter at your database client if the CLI could not infer its
   import.
2. Replace the session placeholder with your authentication lookup. Return
   `{ userId }` for an authenticated request and `null` otherwise.
3. Confirm that `basePath` matches the route mount.

```ts
session: async (request) => {
  const user = await getUserFromRequest(request);
  return user ? { userId: user.id } : null;
},
```

The session resolver protects device, feed, and preference endpoints. Calls to
`push.notify()` are server-side operations; your application must authorize
them before choosing a target user.

## 3. Create the database tables [#3-create-the-database-tables]

For a quick local setup, apply the canonical schema directly:

```bash
npx @better-push/cli migrate
```

To keep schema changes in your existing migration history, run
`npx @better-push/cli generate` and use your normal migration tool instead.
Prisma users must also apply the generated partial-index SQL. See
[Database adapters](/docs/database-adapters) for each path.

## 4. Validate the integration [#4-validate-the-integration]

```bash
npx @better-push/cli doctor
```

`doctor` checks the project layout, environment, schema, providers, route
configuration, queue, and cache. It exits with status 1 when it finds an error,
so the same command can run in CI.

## 5. Verify a delivery locally [#5-verify-a-delivery-locally]

Uncomment the generated development-only `emulator()` provider in `push.ts`,
then start the inbox:

```bash
npx @better-push/cli dev
```

Add a development-only registration control to an authenticated page:

```tsx
"use client";

import { useEmulatorDevice } from "@better-push/core/react";

export function RegisterTestDevice() {
  const device = useEmulatorDevice();

  return (
    <button onClick={() => void device.register()}>
      {device.status === "registered" ? "Test device registered" : "Register test device"}
    </button>
  );
}
```

Register the device, then send from trusted server code using the same user ID:

```ts
await push.notify({
  userId: user.id,
  title: "Hello from better-push",
  body: "The delivery pipeline is working.",
  data: { url: "/notifications" },
});
```

The notification appears in the inbox printed by `better-push dev`. The send
also creates notification and delivery records in Postgres, so this verifies
the normal persistence, routing, and result handling—not a separate mock path.

## Continue the implementation [#continue-the-implementation]

* Configure [Web Push](/docs/web-push-setup), [FCM](/docs/fcm-setup),
  [APNs](/docs/apns-setup), or [Expo](/docs/expo-setup).
* Declare reusable [typed notifications](/docs/typed-notifications).
* Add the [in-app feed](/docs/in-app-feed) and [preferences](/docs/preferences).
* Read [Deployment](/docs/deployment) before choosing inline or
  [queued delivery](/docs/async-delivery).
* Review the [security boundary](/docs/security) before exposing the mounted
  routes.
