8. Knex
TablePlus and psql in our terminal are great for testing out SQL statements, but they can only take us so far since we have to manually run the SQL statements ourselves.
In this lesson, we will learn Knex, a library that allows a Node project to connect to a databases and execute SQL queries. This will enable our server applications to access data from a Postgres database and have a persistent data layer.
Table of Contents
Terms
Knex - a library that allows a Node project to connect to a databases and execute SQL queries.
Deployment Environment - where an application is deployed. The two main ones are:
Development Environment (your own computer) and
Production Environment (a hosting service like Render)
knexfile.js- a file that holds configuration data for connecting to a databaseknex.js- a file that exports aknexobject which has been configured to execute SQL commands to a database.knex.raw(query)- a method used to execute a given SQL query.
Getting Started: Setting up a Database
Take a look at the db.sql file. It contains the SQL commands to create and populate a database called playground. This database will have five tables: people, pets, customers, orders, products.
Turn on your Postgres server
Windows users open your Ubuntu Terminal and run:
sudo service postgresql startMac users open the Postgres app and click Start
Open up the TablePlus SQL query editor (or use the
psqlCLI)Run these commands:
DROP DATABASE playground; CREATE DATABASE playground;Connect to the
playgrounddatabase (or, in thepsqlCLI, run\c playground)

Copy-Paste the commands from
db.sqlinto the TablePlus SQL query editor (or inpsqlconnected to theplaygrounddatabase) and Run All

Mac users can run CMD+R and you will see the tables!
What is Knex?
When we move the data of our server application out of the server's memory and into a database, we need some way of having our server application communicate with the database. That's where Knex comes in.
Knex is a library that allows a Node project to connect to a database and execute SQL queries using that database.
Assuming we already have a database, in order to use Knex in a server application, we must:
Provide connection details (username, password, database name) with a
knexfile.jsCreate a
knexobject to connect to the databaseUse the
knex.rawmethod to execute SQL queries
Configuring Knex
0) Installing modules
We will be using the knex and the pg modules from NPM:
npm i knex pg pg is the module that helps knex connect to a Postgres database. If we were to use a different kind of database, we would need to install a different module alongside knex.
1) Provide connection details with a knexfile.js
knexfile.jsIn order to use a database, we need to tell our server:
the name of the database
the username we will connect to it with
the password for that username.
All of this is done using a special file called knexfile.js:
Run the command npx knex init which will generate a knexfile.js file in the root of your project directory. The knexfile.js holds configuration data for connecting to a database.
⚠️ NOTE: The
knexfile.jsfile MUST be located in the root of your project. Otherwise, otherknexconfigurations won't know where to find it.
The exported object contains configuration objects that can be used for various deployment environments.
// knexfile.js
module.exports = {
development: {}, // Work in progress. Only on your computer
staging: {}, // "Fake" production, fake data, fake users, test integrations
production: {}, // Deployed. Real users, real data.
}For now, we'll be working in the development environment and can wait to set up the other environment configurations.
Each deployment environment needs a client that specifies the kind of database we're connecting to (we will use pg which is short for Postgres).
development: {
client: 'pg',
connection: {
user: 'postgres', // unless you want to use a different user
password: 'postgres', // unless you changed your password
database: 'playground',
// the database name ^
}
},The connection object is where we provide the username, password, and specific database we want to connect to.
2) Create a knex object to connect to the database
knex object to connect to the databaseTo actually use the database details specified by the knexfile.js, we need to create a knex object.
const makeKnex = require('knex');
const knexConfigs = require('./knexfile.js')
const env = process.env.NODE_ENV || 'development';
const knex = makeKnex(knexConfigs[env]);
module.exports = knex;The
knexmodule exports a function for creating a database connection. To use that function, we need a configuration fromknexfile.jsSince our
knexfile.jsexports 3 configurations (development,stagingandproduction), we specify which of those configurations we use with theenvvariableWe use
"development"unless theNODE_ENVenvironment variable is set. When we deploy using Render.com, it will provide aNODE_ENVenvironment variable set to"production".
Finally, we create a
knexobject by invokingmakeKnexand providingknexConfigs[env]as an argument.The
knexobject represents our connection to the database specified inknexfile.js.We export it so that other files can use the
knexconnection object.
3) Use the knex connection object to execute queries
knex connection object to execute queriesWe can play with our knex connection directly in our index.js file.
💡 NOTE: In future projects, only our
modelswill interact withknex.
The knex connection object has an asynchronous method called raw that takes in SQL statements and returns a result object.
// index.js
const knex = require('./db/knex.js');
const getPets = async () => {
// knex.raw returns a query result object
let result = await knex.raw("SELECT * FROM pets");
// .rows is an array containing the query data
return result.rows;
};
const getPeople = async () => {
// often, we just destructure the rows and return
let { rows } = await knex.raw("SELECT * FROM pets");
return rows;
};
const main = async () => {
const pets = await getPets()
const people = await getPeople();
// Destroy the connection to the database.
knex.destroy();
};
main();Most of the time, we'll use the
.rowsproperty to get the results as an array.Without the
knex.destroy(), the file will not terminate.
Writing queries using knex.raw
knex.rawNow that we have knex set up, let's have some fun!
Multi-Line Queries
// Use `` to create multi-line strings
const getPeople = async () => {
const query = `
SELECT *
FROM people;
`
const { rows } = await knex.raw(query);
return rows;
};Dynamic Queries
const createPet = async (name, type, owner_id) => {
// The ? are placeholders
const query = `
INSERT INTO pets (name, type, owner_id)
VALUES (?, ?, ?)
`
// The array passed to knex.raw assigns values to the ? in order
const { rows } = await knex.raw(query, [name, type, owner_id]);
return rows;
};In the
querystring, the?s act as placeholders.To assign values to the
?s, we pass an array of ordered values as a second argument toknex.raw.Order matters! In this example,
namewill be the first?, thentype, thenowner_id.
To avoid SQL injection attacks, we want to avoid inserting dynamic values into a SQL query through interpolation:
${}
A more complex example
Consider the pets table below.
1
Khalo
dog
3
2
Juan Pablo
dog
2
3
Bora
bird
1
4
Tora
dog
1
5
Frida
cat
3
6
Pon Juablo
cat
2
7
Kora
dog
1
Q: What is the SQL query to find the name and id of the dogs owned by Ann Duong?
To turn this query into a function that can show us the pets of ANY given type owned by ANY given owner_id, we will need to create a dynamic query:
const getPetsByOwnerNameAndType = async (ownerName, type) => {
const query = `
SELECT pets.name, pets.id
FROM pets
JOIN people ON pets.owner_id = people.id
WHERE people.name=? AND pets.type=?
`
const { rows } = await knex.raw(query, [ownerName, type]);
console.log(rows);
return rows;
}In the query, the
?act as placeholders.If we pass an array of values as a second argument to
knex.raw, we assign values for those placeholders.In this query, the first
?will be replaced by the value of theownerNameparameter, and the second?will be replaced by the value of thetypeparameter.
Create, Update, and Delete
So far, we've read from the database, let's create, update, and delete using knex.raw.
Create a pet:
const createPet = async(name, type, ownerId) => {
const query = `
INSERT INTO pets (name, type, owner_id)
VALUES (?, ?, ?)
RETURNING *
`
const { rows } = await knex.raw(query, [name, type, ownerId]);
return rows[0];
};RETURNING *returns the created record. Without this,result.rowswill be an empty array.result.rows[0]will be the one created value.
Update a pet's name:
const updatePetNameByName = async(oldName, newName) => {
const query = `
UPDATE pets
SET name=?
WHERE name=?
RETURNING *
`
let { rows } = await knex.raw(query, [newName, oldName]);
console.log(rows[0]);
}Delete a pet:
const deletePetByName = async(name) => {
const query = `
DELETE FROM pets
WHERE name=?
RETURNING *
`
let { rows } = await knex.raw(query, [name]);
console.log(rows[0]);
};Challenges
These challenges illustrate many-to-many relationships:

authors, books, and author_book
get all the books that a certain author has ever written.
get all the authors of a certain book.
create a new book, by a provided author (make sure to connect them!)
update the title of a book
delete a book (make sure to remove the associated connection as well)
customers, products, and orders
get all the orders the belong to certain customer.
get all the products that a certain customer has ever bought.
get the top 3 most recent orders.
get the cheapest product.
get all the customers that have ever bought a certain product.
create a new order
delete an order
update an order
Last updated