---
title: PostgreSQL Store
description: Production-ready PostgreSQL store using Grove ORM.
---

The PostgreSQL store uses [Grove ORM](https://github.com/xraph/grove) with the `pgdriver` for database access. It is the recommended store for production deployments that need ACID transactions and full SQL query power.

## Usage

### Standalone

```go
import (
    "github.com/xraph/grove"
    _ "github.com/xraph/grove/drivers/pgdriver"
    pgstore "github.com/xraph/relay/store/postgres"
)

db, err := grove.Open("pg", "postgres://user:pass@localhost:5432/relay?sslmode=disable")
if err != nil {
    log.Fatal(err)
}

s := pgstore.New(db)

if err := s.Migrate(ctx); err != nil {
    log.Fatal(err)
}

r, err := relay.New(relay.WithStore(s))
```

### With Forge extension

When using the Forge extension with Grove, the store is auto-constructed from the DI container:

```go
app.Use(extension.New(
    extension.WithGroveDatabase(""),  // uses the default grove.DB
))
```

Or via YAML config:

```yaml
extensions:
  relay:
    grove_database: ""
```

See the [Forge Extension guide](/docs/relay/guides/forge-extension) for details.

## Migrations

Call `s.Migrate(ctx)` before first use. Migrations are managed by the Grove migrator with Go-defined migration functions. The migration group is named `"relay"` and creates five tables:

- `relay_event_types` -- event type catalog
- `relay_endpoints` -- webhook endpoints
- `relay_events` -- inbound events
- `relay_deliveries` -- delivery queue with `FOR UPDATE SKIP LOCKED` dequeue
- `relay_dlq` -- dead letter queue

## Internals

| Aspect | Detail |
|--------|--------|
| Driver | Grove ORM with `pgdriver` |
| Migrations | Grove migrator with Go-defined migrations |
| Dequeue | `FOR UPDATE SKIP LOCKED` for concurrent delivery polling |
| Transactions | Database-level ACID |
| JSON fields | `JSONB` for schema, metadata, headers, payload |
| Timestamps | `TIMESTAMPTZ` |
| Ping | `db.Ping(ctx)` |
| Close | Closes the Grove connection |

## When to use

- Production deployments requiring durability and ACID guarantees.
- Multi-process environments with concurrent delivery workers.
- When you need `FOR UPDATE SKIP LOCKED` for efficient dequeue under concurrency.
