Creating a Node.js API with Knex and MySQL
Knex.js and MySQL are essential components commonly used in modern web development for managing and interacting with databases. Knex.js, a JavaScript query builder, simplifies database management, while MySQL, a widely-used relational database management system, allows for efficient data storage and retrieval. This technical article will explore the process of creating a Node API using Knex.js and MySQL, and you will discover how to build complex SQL queries for selecting, inserting, updating, and deleting data.
Getting started
Before you can use MySQL, you have to create a MySQL database. You can create the database locally by installing MySQL on your system. Alternatively, you can create a MySQL database online using services like db4free.
After setting up your MySQL database, create a new project and cd
into it by running the command below:
mkdir mysql-knex && cd mysql-knex
Next, initialize npm with all its defaults by running the command below:
npm init -y
Next, install Knex and the MySQL database driver by running the command below:
npm install knex mysql
To create an express server and manage your environmental variables, run the command below:
npm install express dotemv
Then, create a .env
file in your project’s root directory and store your database credentials.
For example:
DB_HOST =
DB_USER =
DB_PASSWORD =
DB_NAME =
Setting up an Express Server
This tutorial will feature a Bookstore API to demonstrate combining Knex and MySQL with Node.js.
To create an Express server, first, create an index.js
file in your project’s root directory and add the code block below to it:
//index.js
const express = require("express");
const app = express();
const port = 3000;
app.use(express.json());
app.listen(port, () => {
console.log(`App listening on port:${port}`);
});
The code block above creates a simple express server. It listens on port 3000
and uses the express.json()
middleware to parse incoming JSON requests.
Configuring Knex with MySQL
Knex requires a knexfile containing configuration options for connecting to a database, such as a database type, host, port, username, password, and other configuration options.
Run the command below to create a knexfile:
knex init
This command above will create a knexfile.js
file in your project’s root directory. By default, the generated knexfile.js
file will have sqlite3
as its development database.
To configure Knex to use MySQL, replace the contents of your knexfile.js
file with the code block below:
require("dotenv").config()
/**
* @type { Object.<string, import("knex").Knex.Config> }
*/
module.exports = {
development: {
client: "mysql",
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
},
migrations: {
directory: "./db/migrations",
}
}
};
The code block above configures Knex to use MySQL as its database driver and specifies the file path where the migration files will be stored.
Next, create a db
folder in your project directory by running the command below:
mkdir db
Then, create a db.js
file in your db
folder and import knex
and your knexFile.js
file.
Like so:
//db/db.js
const knex = require("knex");
const knexFile = require("../knexfile.js");
Next, add the code block below to your db.js file:
//db/db.js
const environment = process.env.NODE_ENV || "development";
module.exports = knex(knexFile[environment]);
The code block above sets the environment
variable to either the NODE_ENV
environment variable or development
if NODE_ENV
is not set. This lets you specify different configurations for different environments, such as development, production, or testing.
Creating Migration Files
Migration files are files you can use to manage changes to the database schema. When you create a new table, modify an existing table, or add new columns, you must modify the database schema to reflect these changes.
Your Bookstore API will have three database tables: Books, Authors, and Genres. Thus, you will need three migration files.
Run the command below to create your books
migration file:
knex migrate:make books
Run the command below to create your authors
migration file:
knex migrate:make authors
Run the command below to create your genres
migration file:
knex migrate:make genres
Running the commands above will create three migration files in your knexfile.js
specified file path.
Writing Migrations
The migration files generated by Knex contain boilerplate code with no connection to the type of application you are building.
Open your authors
migration file and replace the up
function with the code block below:
exports.up = function(knex) {
return knex.schema.createTable("authors", (table) => {
table.increments("id").primary();
table.string("name").notNullable();
table.timestamps(true, true);
})
};
The code block above, when executed, creates an “authors” table in your database with the tables specified above.
Next, replace the down
function with the code block below:
exports.down = function(knex) {
return knex.schema.dropTableIfExists("authors");
};
The code block above, when executed, drops the “authors” table in your database, which is the opposite of what the up
function does.
Next, open your genres
migration file and replace the up
function with the code block below:
exports.up = function (knex) {
return knex.schema.createTable("genres", (table) => {
table.increments("id").primary();
table.string("name").notNullable();
table.timestamps(true, true);
});
};
The code block above, when executed, creates a “genres” table in your database with the tables specified above.
Next, replace the down
function with the code block below:
exports.down = function (knex) {
return knex.schema.dropTableIfExists("genres");
};
The code block above, when executed, drops the “genres” table in your database, which is the opposite of what the up
function does.
Next, open your books
migration file and replace the up
function with the code block below:
exports.up = function(knex) {
return knex.schema.createTable("books", (table) => {
table.increments("id").primary();
table.string("title").notNullable();
table.integer('author_id').unsigned().notNullable();
table.integer('genre_id').unsigned().notNullable();
table.foreign('author_id').references('authors.id');
table.foreign('genre_id').references('genres.id');
table.timestamps(true, true);
})
};
The code block above, when executed, creates a “books” table in your database with the tables specified above. It also defines a many-to-one relationship between genres, authors, and books.
Next, replace the down
function with the code block below:
exports.down = function(knex) {
return knex.schema.dropTableIfExists("books");
};
The code block above, when executed, drops the “books” table in your database, which is the opposite of what the up
function does.
To run the migrations, run the command below on your terminal:
knex migrate:latest
The command above goes through all your migration files and runs the up
function.
To undo the migrations, run the command below:
knex migrate:rollback
The command above goes through all your migration files and runs the down
function.
The Books table has two rows that depend on the Genres and Authors tables, respectively. So you have to create the Authors and Genres table before you create the Books table.
Alternatively, you can run the migrations in the sequence Authors → Genres → Books by running the command below:
knex migrate:up <migration_filename>
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
Seeding Databases with Data
Seeding refers to populating your database with initial data that you can use for development or testing.
To seed your database with initial data, you must create seed files using Knex.
Run the command below to create a seed file for your author
table:
knex seed:make authors
The command above generates an “authors” seed file in your knexfile.js
specified file path.
Next, replace the generated boilerplate code with the code block below:
//authors.js
exports.seed = async function(knex) {
// Deletes ALL existing entries
await knex('authors').del()
await knex('authors').insert([
{id: 1, name: 'Jane Austen'},
{id: 2, name: 'Ernest Hemingway'},
{id: 3, name: 'Agatha Christie'}
]);
};
The code block above, when executed, deletes all existing entries from the authors
table and inserts the specified entries in the table.
Run the command below to create a seed file for your genres
table:
knex seed:make genres
The command above generates a “genres” seed file in your knexfile.js
specified file path.
Next, replace the generated boilerplate code with the code block below:
//genres.js
exports.seed = async function (knex) {
// Deletes ALL existing entries
await knex("genres").del();
await knex("genres").insert([
{ id: 1, name: "Romance" },
{ id: 2, name: "Mystery" },
{ id: 3, name: "Thriller" },
{ id: 4, name: "Science Fiction" },
]);
};
Run the command below to create a seed file for your books
table:
knex seed:make books
Next, replace the generated boilerplate code with the code block below:
//books.js
exports.seed = async function (knex) {
// Deletes ALL existing entries
await knex("books").del();
await knex("books").insert([
{
id: 1,
title: "Pride and Prejudice",
description: "A novel of manners by Jane Austen",
author_id: 1,
genre_id: 1,
},
{
id: 2,
title: "The Old Man and the Sea",
description: "A short novel by Ernest Hemingway",
author_id: 2,
genre_id: 3,
},
{
id: 3,
title: "Murder on the Orient Express",
description: "A detective novel by Agatha Christie",
author_id: 3,
genre_id: 2,
},
{
id: 4,
title: "Emma",
description: "A novel by Jane Austen",
author_id: 1,
genre_id: 1,
},
{
id: 5,
title: "The Sun Also Rises",
description: "A novel by Ernest Hemingway",
author_id: 2,
genre_id: 1,
},
]);
};
Like the migrations files, you must run the seed files in the order of Authors → Genres, → Books.
You can execute a seed file by running the command below:
knex seed:run --specific=<file_name>
Querying your Database
Create a routes
folder in your project’s root directory and create a bookStore.js
file in it. You are going to implement route handlers that:
- Fetch all the books in the database.
- Fetch books based on their genre.
- Fetch books based on their author.
To implement these routes, import Express, your Knex instance, and set up the Express router.
Like so:
const express = require("express");
const router = express.Router();
const db = require("../db/db.js");
Next, add the code block below to implement the route handler that fetches all the books in the database:
router.get("/books", async (req, res) => {
try {
const books = await db("books");
res.json(books);
} catch (err) {
console.error(err);
res.status(500).send("Internal Server Error");
}
});
Next, add the code block below to implement the route handler that fetches books based on their genre:
router.get("/genres/:id/books", async (req, res) => {
try {
const genreID = req.params.id;
const books = await db("books").where("genre_id", genreID);
const genre = await db("genres").where("id", genreID);
res.json({ genre: genre[0].name, books });
} catch (err) {
console.error(err);
res.status(500).send("Internal Server Error");
}
});
Next, add the code block below to implement the route handler that fetches books based on their author:
router.get("/authors/:id/books", async (req, res) => {
try {
const authorID = req.params.id;
const books = await db("books").where("author_id", authorID);
const author = await db("authors").where("id", authorID);
res.json({ author: author[0].name, books });
} catch (err) {
console.error(err);
res.status(500).send("Internal Server Error");
}
});
Next, export your Express router. Like so:
module.exports = router;
Finally, import your router instance into your index.js
file and use it as middleware. Like so:
const bookRouter = require("./routes/bookStore.js");
app.use("/api", bookRouter);
Testing your Database
To test your application, you can use tools such as Postman to make HTTP requests to your API and verify that it returns the expected results. Alternatively, you can use your browser.
For example, you can request a GET to http://localhost:3000/api/genres/1/books by pasting the URL on your browser’s search bar and hitting ENTER. Your results should be similar to the image below.
Conclusion
In this article, you learned how to use Knex.js with MySQL to build a Node.js Bookstore API. You also learned how to configure Knex to use MySQL as its database client, connect to a PostgreSQL database locally and remotely, seed the database, create migrations, and make queries using Knex.
With the knowledge gained in this article, you can now build robust and scalable Node.js applications that leverage the power of MySQL and the simplicity of Knex.js.