DEV Community

Atlas Whoff
Atlas Whoff

Posted on

PostgreSQL JSON Columns: When to Use JSONB vs Relational Tables

PostgreSQL JSON Columns: When to Use JSONB vs Relational Tables

JSONB gives you document flexibility inside a relational database. Here's when it's the right tool.

JSONB vs JSON

Always use JSONB, not JSON:

  • JSONB is stored as binary — faster for queries
  • JSONB deduplicates keys, normalizes whitespace
  • JSON is stored as-is — only faster for writes you never query

When JSONB Makes Sense

Use JSONB for:

  • Schema-flexible data (user settings, feature configs, plugin metadata)
  • Event payloads where the shape varies by type
  • Third-party API responses you need to store but not query deeply
  • Polymorphic attributes

Use relational tables when:

  • You query individual fields frequently
  • You need foreign keys to this data
  • You need row-level permissions

Basic JSONB Operations

-- Create table with JSONB column
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email TEXT NOT NULL,
  settings JSONB DEFAULT '{}'
);

-- Insert
INSERT INTO users (email, settings)
VALUES ('alice@example.com', '{"theme": "dark", "notifications": {"email": true}}');

-- Query a nested field
SELECT email, settings->>'theme' AS theme FROM users;

-- Filter by nested value
SELECT * FROM users WHERE settings->>'theme' = 'dark';

-- Update a single key without replacing the whole object
UPDATE users SET settings = settings || '{"theme": "light"}' WHERE id = $1;
Enter fullscreen mode Exit fullscreen mode

Indexing JSONB

-- GIN index for @> (contains) queries
CREATE INDEX idx_users_settings ON users USING gin(settings);

-- Expression index for frequent key access
CREATE INDEX idx_users_theme ON users ((settings->>'theme'));

-- Query that uses the GIN index
SELECT * FROM users WHERE settings @> '{"notifications": {"email": true}}';
Enter fullscreen mode Exit fullscreen mode

Prisma with JSONB

// schema.prisma
model User {
  id       String @id @default(uuid())
  email    String @unique
  settings Json   @default("{}")
}
Enter fullscreen mode Exit fullscreen mode
// Update nested settings safely
await prisma.user.update({
  where: { id: userId },
  data: {
    settings: {
      ...currentSettings,
      theme: 'dark',
    },
  },
});

// Query users with specific setting
const darkModeUsers = await prisma.user.findMany({
  where: {
    settings: { path: ['theme'], equals: 'dark' },
  },
});
Enter fullscreen mode Exit fullscreen mode

JSONB for settings/metadata, Prisma schema design, and query optimization are part of the database patterns in the AI SaaS Starter Kit.

Top comments (0)