DeesseJS Collections

Core Concepts

Understanding configuration, collections, and fields

Core Concepts

Understanding the fundamental building blocks of @deessejs/collections: configuration, collections, and fields.

Configuration

All applications start with a defineConfig function that defines your entire data layer.

Basic Configuration

import { defineConfig } from '@deessejs/collections'
import { users, posts } from '../collections'

export const { collections, db } = defineConfig({
  database: {
    url: process.env.DATABASE_URL!
  },

  collections: [users, posts],

  plugins: [
    // Global plugins applied to all collections
  ]
})

What defineConfig Returns

The defineConfig function returns a runtime object with:

  • collections: Fully typed object with all your collections
  • db: The underlying Drizzle instance for raw queries
const { collections, db } = defineConfig({...})

// Collections provide typed operations
collections.users.findMany()
collections.posts.create({ data: {...} })

// db for raw Drizzle queries when needed
import { users } from './schema'
await db.select().from(users)

Collections

Collections are the primary way to organize and interact with your data. Each collection represents a domain concept.

File-Based Collections

Collections are defined in their own files:

// collections/posts.ts
export const posts = collection({
  slug: 'posts',

  // Human-readable info
  label: 'Blog Posts',
  plural: 'Posts',

  fields: {
    title: field({ type: text() }),
    content: field({ type: text() })
  }
})

Collection Object Syntax

Collections use object syntax, not positional parameters:

// ✅ Correct - object syntax
collection({
  slug: 'posts',
  fields: { ... }
})

// ❌ Not supported - positional parameters
collection('posts', { ... })

What Collections Provide

// Collection name becomes a property
collections.posts.findMany()
collections.users.findMany()

// Automatic operations
.findMany()
.findUnique({ where: { id: 1 } })
.findFirst({ where: { ... } })
.create({ data: { ... } })
.update({ where: { id: 1 }, data: { ... } })
.delete({ where: { id: 1 } })
.count({ where: { ... } })

Collection Composition

Collections compose from:

  • Fields: Data structure
  • Hooks: Behavior at operation boundaries
  • Plugins: Extensions and cross-cutting concerns
  • Validation: Data integrity rules
export const posts = collection({
  slug: 'posts',

  fields: { ... },

  hooks: {
    beforeCreate: [async ({ data }) => {
      data.slug = slugify(data.title)
    }]
  },

  plugins: [
    slugPlugin({ from: 'title' })
  ]
})

Fields

Fields are the atomic units of your data model. They define what data your collections contain.

Field Definition Syntax

Fields use object syntax within the fields property:

fields: {
  // ✅ Correct - object syntax
  title: field({
    type: text({ min: 3, max: 255 })
  }),

  // ❌ Not supported - array syntax
  // [field({ type: text() })]
}

Field Type Structure

Each field is created with field() and a field type:

field({
  type: text({ min: 3, max: 255 })
})

Field types combine:

  • Zod schema: For validation and TypeScript types
  • Drizzle column: For database mapping

Type Inference

Types are automatically inferred from your field definitions:

export const posts = collection({
  slug: 'posts',

  fields: {
    title: field({ type: text() }),
    views: field({ type: number() }),
    published: field({ type: boolean() })
  }
})

// TypeScript automatically infers:
// type Post = {
//   id: number
//   title: string
//   views: number
//   published: boolean
//   createdAt: Date
//   updatedAt: Date
// }

This means:

  • ✅ Zero manual type definitions
  • ✅ Autocomplete everywhere
  • ✅ Type-safe queries
  • ✅ Refactoring confidence

Note: While you can define collections inline in defineConfig, the recommended approach is file-based collections for better organization and maintainability.

Next Steps

On this page