← Back to DevBytes

Building Full-Stack Apps with Knex

Building Full-Stack Apps with Knex

What is Knex?

Knex.js is a SQL query builder for Node.js that supports multiple database dialects including PostgreSQL, MySQL, SQLite3, and MSSQL. It provides a programmatic way to construct and execute SQL queries, manage database schema via migrations, and populate tables with seed data. Knex is not an ORM (Object-Relational Mapper) – it sits at a lower level, giving you direct control over SQL while abstracting away dialect-specific syntax.

Why Knex Matters in Full-Stack Development

In a full‑stack application, the database layer is critical. Knex helps you:

Setting Up Knex in a Full-Stack Project

Start by installing Knex and your preferred database driver. For this tutorial we will use PostgreSQL:

npm install knex pg

Initialize Knex in your project to create a knexfile.js:

npx knex init

Edit the generated knexfile.js to match your environment. A typical configuration looks like this:

// knexfile.js
module.exports = {
  development: {
    client: 'pg',
    connection: {
      host: process.env.DB_HOST || 'localhost',
      database: process.env.DB_NAME || 'myapp_dev',
      user:     process.env.DB_USER || 'postgres',
      password: process.env.DB_PASS || 'password'
    },
    migrations: {
      directory: './db/migrations'
    },
    seeds: {
      directory: './db/seeds'
    }
  },
  production: {
    client: 'pg',
    connection: process.env.DATABASE_URL,
    migrations: { directory: './db/migrations' },
    seeds: { directory: './db/seeds' }
  }
};

Now create a Knex instance in your application:

// db.js
const knex = require('knex');
const config = require('./knexfile');
const environment = process.env.NODE_ENV || 'development';

module.exports = knex(config[environment]);

Creating Migrations and Seeds

Migrations allow you to define and version your database schema. Create a migration for a users table:

npx knex migrate:make create_users

This generates a file in ./db/migrations/. Edit it to define the table:

// db/migrations/YYYYMMDDHHMMSS_create_users.js
exports.up = function(knex) {
  return knex.schema.createTable('users', (table) => {
    table.increments('id').primary();
    table.string('username', 100).notNullable().unique();
    table.string('email', 255).notNullable().unique();
    table.timestamp('created_at').defaultTo(knex.fn.now());
  });
};

exports.down = function(knex) {
  return knex.schema.dropTableIfExists('users');
};

Run the migration to apply it:

npx knex migrate:latest

Seeds populate your database with sample data. Create a seed file:

npx knex seed:make 01_users

Edit the generated file:

// db/seeds/01_users.js
exports.seed = function(knex) {
  return knex('users').del()
    .then(function () {
      return knex('users').insert([
        { username: 'alice', email: 'alice@example.com' },
        { username: 'bob', email: 'bob@example.com' }
      ]);
    });
};

Run seeds:

npx knex seed:run

Using Knex in Express Routes (Full-Stack Integration)

Now we integrate Knex into a typical Express full‑stack backend. Assume you already have Express installed. Create an app.js file:

// app.js
const express = require('express');
const knex = require('./db'); // our Knex instance
const app = express();
app.use(express.json());

// GET /api/users – fetch all users
app.get('/api/users', async (req, res) => {
  try {
    const users = await knex('users').select('*');
    res.json(users);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to fetch users' });
  }
});

// POST /api/users – create a new user
app.post('/api/users', async (req, res) => {
  const { username, email } = req.body;
  if (!username || !email) {
    return res.status(400).json({ error: 'username and email required' });
  }
  try {
    const [newUser] = await knex('users')
      .insert({ username, email })
      .returning('*');  // PostgreSQL requires .returning()
    res.status(201).json(newUser);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to create user' });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Notice how Knex returns promises. Using async/await keeps the code clean. The .returning() method is specific to PostgreSQL; for MySQL or SQLite you can omit it and rely on the inserted id via insert returning the id array.

Best Practices

Conclusion

Knex.js is a powerful tool for building full‑stack applications because it bridges the gap between raw SQL and high‑level ORMs. It gives you the flexibility to write complex queries, maintain schema changes with migrations, and keep your code database‑agnostic. By integrating Knex with Express (or any Node.js framework) you can quickly build robust API endpoints that interact with your database securely and efficiently. Start small – create a migration, write a seed, then connect it to your routes. As your application grows, you’ll appreciate the control and clarity Knex brings to your data layer.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles