Skip to main content
The @feathersjs/knex adapter provides SQL database integration through Knex.js, supporting PostgreSQL, MySQL, SQLite, MSSQL, Oracle, and other SQL databases with transactions, joins, and advanced querying.

Installation

1

Install dependencies

Install the Knex adapter, Knex.js, and your database driver:
npm install @feathersjs/knex knex pg
2

Configure Knex

Create a Knex instance:
import knex from 'knex'

const db = knex({
  client: 'pg',
  connection: {
    host: 'localhost',
    port: 5432,
    user: 'postgres',
    password: 'password',
    database: 'myapp'
  },
  pool: {
    min: 2,
    max: 10
  }
})
3

Create a service

Create a service using the Knex adapter:
import { KnexService } from '@feathersjs/knex'

class UserService extends KnexService {
  // Custom methods here
}

app.use('users', new UserService({
  Model: db,
  name: 'users',
  paginate: {
    default: 10,
    max: 50
  }
}))

Configuration

Service Options

The Knex adapter requires these options:
interface KnexAdapterOptions {
  Model: Knex                    // Knex instance
  name: string                   // Table name
  id?: string                    // Primary key (default: 'id')
  schema?: string                // Database schema
  paginate?: PaginationOptions   // Pagination settings
  multi?: boolean | string[]     // Allow multi operations
  events?: string[]              // Custom events
  extendedOperators?: object     // Custom query operators
}

Example Configuration

import { KnexService } from '@feathersjs/knex'

app.use('messages', new KnexService({
  Model: db,
  name: 'messages'
}))

Querying

Basic Queries

The Knex adapter supports all standard Feathers query operators:
// Find all users
const users = await app.service('users').find({
  query: {}
})

// Find with filters
const activeUsers = await app.service('users').find({
  query: {
    status: 'active',
    age: { $gte: 18 }
  }
})

SQL-Specific Operators

The Knex adapter includes SQL-specific operators:
// Case-sensitive pattern matching
const users = await app.service('users').find({
  query: {
    name: { $like: '%John%' }
  }
})

// NOT LIKE
const users = await app.service('users').find({
  query: {
    email: { $notlike: '%spam.com' }
  }
})

Raw Knex Queries

Pass custom Knex query builders:
import { KnexAdapterParams } from '@feathersjs/knex'

// Use custom Knex query
const params: KnexAdapterParams = {
  knex: db('users')
    .select('users.*', 'profiles.bio')
    .leftJoin('profiles', 'users.id', 'profiles.userId')
    .where('users.status', 'active')
}

const results = await app.service('users').find(params)

Data Manipulation

Create

const user = await app.service('users').create({
  name: 'Alice',
  email: '[email protected]',
  age: 25
})

Update

// Replace entire row (except ID)
const updated = await app.service('users').update(
  1,
  {
    name: 'Alice Updated',
    email: '[email protected]',
    age: 26
  }
)

Patch

// Partial update
const patched = await app.service('users').patch(
  1,
  { status: 'verified' }
)

Remove

const removed = await app.service('users').remove(1)

Transactions

The Knex adapter provides powerful transaction support:

Using Transaction Hooks

import { transaction } from '@feathersjs/knex'

app.service('accounts').hooks({
  before: {
    create: [transaction.start()]
  },
  after: {
    create: [transaction.end()]
  },
  error: {
    create: [transaction.rollback()]
  }
})

// Transaction automatically managed
const account = await app.service('accounts').create({
  balance: 1000
})

Transaction Context

Transactions maintain context across hooks:
app.service('transfers').hooks({
  before: {
    create: [
      transaction.start(),
      async (context) => {
        const { fromAccount, toAccount, amount } = context.data
        
        // Deduct from source account (uses transaction)
        await app.service('accounts').patch(
          fromAccount,
          { balance: db.raw('balance - ?', [amount]) },
          context.params  // Transaction passed automatically
        )
        
        // Add to destination account (uses same transaction)
        await app.service('accounts').patch(
          toAccount,
          { balance: db.raw('balance + ?', [amount]) },
          context.params
        )
        
        return context
      }
    ]
  },
  after: {
    create: [transaction.end()]
  },
  error: {
    create: [transaction.rollback()]
  }
})

Advanced Features

Table Schemas

Create and manage database schemas:
// Create table
await db.schema.createTable('users', (table) => {
  table.increments('id').primary()
  table.string('email').unique().notNullable()
  table.string('name').notNullable()
  table.integer('age')
  table.string('status').defaultTo('active')
  table.timestamps(true, true)
})

// Add indexes
await db.schema.alterTable('users', (table) => {
  table.index('email')
  table.index(['status', 'createdAt'])
})

Joins and Relations

Perform joins using custom Knex queries:
const usersWithProfiles = await app.service('users').find({
  knex: db('users')
    .select(
      'users.id',
      'users.name',
      'users.email',
      'profiles.bio',
      'profiles.avatar'
    )
    .leftJoin('profiles', 'users.id', 'profiles.userId')
    .where('users.status', 'active')
})

Field Selection

// Select specific fields
const users = await app.service('users').find({
  query: {
    $select: ['id', 'name', 'email'],
    status: 'active'
  }
})

// The adapter automatically includes the ID field

Type Safety

Full TypeScript support with generics:
import { KnexService, KnexAdapterParams } from '@feathersjs/knex'
import type { Params } from '@feathersjs/feathers'
import type { Knex } from 'knex'

interface User {
  id: number
  email: string
  name: string
  age: number
  status: 'active' | 'inactive'
  createdAt: Date
  updatedAt: Date
}

interface UserData {
  email: string
  name: string
  age: number
  status?: 'active' | 'inactive'
}

interface UserParams extends KnexAdapterParams {
  user?: User
}

class UserService extends KnexService<User, UserData, UserParams> {
  async find(params?: UserParams) {
    // Fully typed
    return super.find(params)
  }
}

const db: Knex = knex({ /* config */ })

app.use('users', new UserService({
  Model: db,
  name: 'users'
}))

// Type-safe usage
const users: User[] = await app.service('users').find()
const user: User = await app.service('users').get(1)

Error Handling

The adapter converts SQL errors to Feathers errors:
import { errorHandler } from '@feathersjs/knex'

try {
  await app.service('users').create({
    email: '[email protected]'
  })
} catch (error) {
  // SQL errors are converted:
  // Unique constraint -> Conflict (409)
  // Foreign key -> BadRequest (400)
  // Not found -> NotFound (404)
}

Connection Pooling

Configure connection pooling for better performance:
const db = knex({
  client: 'pg',
  connection: process.env.DATABASE_URL,
  pool: {
    min: 2,
    max: 10,
    createTimeoutMillis: 3000,
    acquireTimeoutMillis: 30000,
    idleTimeoutMillis: 30000,
    reapIntervalMillis: 1000,
    createRetryIntervalMillis: 100
  }
})

// Clean up on shutdown
process.on('SIGTERM', async () => {
  await db.destroy()
})

Next Steps

MongoDB Adapter

Learn about the MongoDB adapter

Common Patterns

Explore adapter patterns

Hooks

Add hooks to your services

Validation

Validate your data with schemas

Build docs developers (and LLMs) love