go-notificationgo-notification
Channels

Database (In-App)

Persist notifications to your database for in-app notification centers. Postgres, MySQL, and SQLite supported.

The database channel stores notifications in a table so your app can show an in-app notification center — unread counts, mark-as-read, and a query API — without wiring anything external.

Supported databases

  • PostgreSQL (9.6+)
  • MySQL (5.7+ / MariaDB 10.3+)
  • SQLite (3.x)

All three use the same channel through a Store. The built-in SQLStore accepts any *sql.DB.

Setup

The channel is configured with a Store, not a raw *sql.DB. Build a SQLStore with database.NewSQLStore(db, dialect):

main.go
import (
    "database/sql"
    _ "github.com/lib/pq"

    notification "github.com/gopackx/go-notification"
    "github.com/gopackx/go-notification/channel/database"
)

db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil { panic(err) }

store := database.NewSQLStore(db, database.DialectPostgreSQL) // or DialectMySQL, DialectSQLite

notifier.RegisterChannel(database.New(database.Config{
    Store: store,
}))

The channel registers itself as "database". To customize the table name, set store.Table before registering (default "notifications").

Migration

The schema lives in the separate migrate package. Call migrate.Up at boot to create the table (idempotent — uses CREATE TABLE IF NOT EXISTS):

main.go
import "github.com/gopackx/go-notification/migrate"

if err := migrate.Up(ctx, db, database.DialectPostgreSQL, "notifications"); err != nil {
    panic(err)
}

migrate.Down(ctx, db, dialect, table) drops the table (intended for tests). The migrate package is the single source of truth for the schema, so teams managing migrations elsewhere can copy the statements into their own tooling.

Sending

ToDatabase returns a *database.Message built with the fluent API — not a map:

main.go
func (n OrderShipped) Via(u notification.Notifiable) []string {
    return []string{"database"}
}

func (n OrderShipped) ToDatabase(u notification.Notifiable) *database.Message {
    return database.NewMessage().
        SetType("order.shipped").
        SetTitle("Order shipped").
        SetBody("Your order has shipped.").
        AddData("order_id", n.OrderID)
}

Each row stores: an auto id, type, title, body, the recipient's notifiable_type (from the optional GetNotifiableType(), default "notifiables") and notifiable_id (from GetID()), a JSON data blob, read_at (nullable), and created_at.

Query API

Reach the channel via notifier.Channel("database") and type-assert to *database.Channel. Every method takes the Notifiable (not loose type/id strings):

main.go
ch := notifier.Channel("database").(*database.Channel)

// Most recent, paginated
items, err := ch.List(ctx, user, database.ListOptions{Limit: 20, Offset: 0})

// Only unread (most recent first)
unread, err := ch.Unread(ctx, user, 20)

// Unread count
count, err := ch.CountUnread(ctx, user)

// Mark one / all as read
err = ch.MarkAsRead(ctx, notificationID)
err = ch.MarkAllAsRead(ctx, user)

// Fetch / delete by id
one, err := ch.Get(ctx, notificationID)   // returns database.ErrNotFound if missing
err = ch.Delete(ctx, notificationID)

List/Unread return []database.StoredNotification (ID, Type, Title, Body, Data, ReadAt *time.Time, CreatedAt, …) — ready to render without re-parsing.

database.ListOptions fields: Limit (default 50), Offset, UnreadOnly bool, and Type string (filter by notification type).

Configuration reference

FieldTypeRequiredDescription
StoreStoreyesPersistence backend. Use database.NewSQLStore(db, dialect).
NamestringnoOverride the channel name. Default: "database".

SQLStore fields you may tune: DB, Dialect, Table (default "notifications").

Custom stores

Store is an interface (Insert, List, Get, MarkAsRead, MarkAllAsRead, CountUnread, Delete). Implement it for sharded SQL, Redis, or a document store, then pass your implementation as Config.Store.

Why no ORM?

This is deliberate. go-notification uses database/sql only — no GORM, no Bun, no Ent. The database channel owns its own table and queries, so it won't interfere with whatever ORM the rest of your app uses.

Pairing with real-time push

The database channel only writes to the DB. For real-time updates (a bell icon that updates without a refresh), combine it with:

  • A second channel (WebSocket or server-sent events) triggered on the same notification.
  • Or poll CountUnread on a timer from the client.