Marcy Lab School Docs
  • Welcome
  • Student Guidelines & Policies
    • Student Handbook
    • AI Policy
    • Academic Calendar
  • Environment Setup
    • Local Environment Setup - Mac
    • Local Environment Setup - Windows
    • GitHub Setup
    • Postgres Setup
  • How-Tos
    • How To Code at Marcy: Code Style Guide
    • How to Do Short Response and Coding Assignments
    • How to Debug
    • How to PEDAC
    • How to Create A GitHub Organization and Scrumboard
    • How to Create Projects with Vite
    • How to Deploy on GitHub Pages
    • How to Deploy on Render
    • How to Test your API with Postman
  • Mod 0 - Command Line Interfaces, Git, and GitHub
    • Overview
    • 1. Command Line Interfaces
    • 2. Git & GitHub
    • 3. Git Pulling & Merging
    • 4. Git Branching & PRs
  • Mod 1 - JavaScript Fundamentals
    • Overview
    • 1. Intro to Programming
    • 2. Errors
    • 3. Node & Node Modules
    • 4. Variables, Functions & String Methods
    • 5. Control Flow, typeof, and Math
    • 6. Loops
    • 7. Arrays
    • 8. Objects
    • 9. Higher Order Functions: Callbacks
    • 10. Higher Order Functions: Array Methods
    • 11. Regex
  • Mod 2 - HTML, CSS & the DOM
    • Overview
    • 1. HTML
    • 2. CSS
    • 3. Accessibility (a11y)
    • 4. The Document Object Model (DOM) API
    • 5. Events
    • 6. Forms
    • 7. The Box Model and Positioning
    • 8. Flexbox
    • 9. Grid & Media Queries
    • 10. ESModules
    • 11. Vite
    • 12. LocalStorage
  • Mod 3 - Async & APIs
    • Overview
    • 1. Promises
    • 2. Fetch
    • 3. Building a Fetching App
    • 4. Async & Await
    • 5. A Generic Fetch Handler
  • Mod 4 - Project Week!
    • Important How Tos and Guides
      • How to Create a GitHub Organization and Scrum Board
      • How To Start a Project with Vite
      • How To Deploy a Project with GitHub Pages
    • Project Week Overview
    • Agile Methodologies
    • Deliverables & Milestones
    • Technical Requirements Checklist
    • Free API List
    • Collaborative GitHub
  • Mod 5 - Object-Oriented Programming
    • Overview
    • 1. Intro to OOP, Encapsulation, Factory Functions, and Closure
    • 2. Classes
    • 3. Private & Static
    • 4. UML Diagrams & Has Many/Belongs To Relationships
    • 5. Challenge: Implementing Has Many/Belongs To
    • 6. Inheritance
    • 7. Polymorphism
    • 8. Review and Practice
    • MDN: Object Prototypes
  • Mod 6 - Data Structures & Algorithms
    • Overview
    • Important How Tos and Guides
      • How to Debug
      • How to PEDAC
    • 1. Nodes & Linked Lists
    • 2. Singly & Doubly Linked Lists
    • 3. Stacks & Queues
    • 4. Recursion
    • 5. Trees
  • Mod 7 - React
    • Overview
    • Important How Tos and Guides
      • How to Create Projects with Vite
      • How to Deploy on GitHub Pages
    • 1. Intro to React
    • 2. Events, State, and Forms
    • 3. Fetching with useEffect
    • 4. React Router
    • 5. Building a Flashcards App
    • 6. React Context
    • 7. Global Context Pattern
  • Mod 8 - Backend
    • Overview
    • Important How Tos and Guides
      • How to Deploy on Render
      • How to Test your API with Postman
      • Postgres Setup
    • 1. Intro to Express
    • 2. Building a Static Web Server with Middleware
    • 3. Securing API Keys and Environment Variables
    • 4. RESTful CRUD API
    • 5. Model-View-Controller Architecture
    • 6. SQL and Databases
    • 7. JOIN (Association) SQL Queries
    • 8. Knex
    • 9. Your First Fullstack App!
    • 10. Migrations & Seeds
    • 11. Schema Design & Normalization
    • 12. Hashing Passwords with Bcrypt
    • 13. React Express Auth Template Overview
  • Mod 9 - Civic Tech Hackathon
    • Overview
    • Rubric
  • Mod 10 - Capstone
    • Overview
Powered by GitBook
On this page
  • Terms
  • Getting Started: Setting up a Database
  • What is Knex?
  • Configuring Knex
  • 0) Installing modules
  • 1) Provide connection details with a knexfile.js
  • 2) Create a knex object to connect to the database
  • 3) Use the knex connection object to execute queries
  • Writing queries using knex.raw
  • Multi-Line Queries
  • Dynamic Queries
  • A more complex example
  • Create, Update, and Delete
  • Challenges
  1. Mod 8 - Backend

8. Knex

Previous7. JOIN (Association) SQL QueriesNext9. Your First Fullstack App!

Last updated 1 month ago

Follow along with code examples !

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 , 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 database

  • knex.js - a file that exports a knex object 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.

  1. Turn on your Postgres server

    • Windows users open your Ubuntu Terminal and run: sudo service postgresql start

    • Mac users open the Postgres app and click Start

  2. Open up the TablePlus SQL query editor (or use the psql CLI)

  3. Run these commands:

    DROP DATABASE playground;
    CREATE DATABASE playground;
  4. Connect to the playground database (or, in the psql CLI, run \c playground)

  1. Copy-Paste the commands from db.sql into the TablePlus SQL query editor (or in psql connected to the playground database) and Run All

  1. 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:

  1. Provide connection details (username, password, database name) with a knexfile.js

  2. Create a knex object to connect to the database

  3. Use the knex.raw method 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

In 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.js file MUST be located in the root of your project. Otherwise, other knex configurations 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

To 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 knex module exports a function for creating a database connection. To use that function, we need a configuration from knexfile.js

  • Since our knexfile.js exports 3 configurations (development, staging and production), we specify which of those configurations we use with the env variable

  • Finally, we create a knex object by invoking makeKnex and providing knexConfigs[env] as an argument.

    • The knex object represents our connection to the database specified in knexfile.js.

    • We export it so that other files can use the knex connection object.

3) Use the knex connection object to execute queries

We can play with our knex connection directly in our index.js file.

💡 NOTE: In future projects, only our models will interact with knex.

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 .rows property to get the results as an array.

  • Without the knex.destroy(), the file will not terminate.

Writing queries using knex.raw

Now 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 query string, the ?s act as placeholders.

  • To assign values to the ?s, we pass an array of ordered values as a second argument to knex.raw.

    • Order matters! In this example, name will be the first ?, then type, then owner_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.

id
name
type
owner_id

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?

Answer
SELECT pets.name, pets.id
FROM pets
  JOIN people ON pets.owner_id = people.id
WHERE people.name='Ann Duong' AND pets.type='dog'

Explanation: We have to specify that we want the name and id columns from the pets table since the people table also has columns with those names. We then query from the join of pets and people, connecting rows from each table using the pets.owner_id foreign key and the people.id primary key. We finally filter the results to only show the rows where the person's name is Ann Duong and the pet's type is dog.

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 the ownerName parameter, and the second ? will be replaced by the value of the type parameter.

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.rows will 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

We use "development" unless the NODE_ENV environment variable is set. , it will provide a NODE_ENV environment variable set to "production".

When we deploy using Render.com
here
Knex
Terms
Getting Started: Setting up a Database
What is Knex?
Configuring Knex
0) Installing modules
1) Provide connection details with a knexfile.js
2) Create a knex object to connect to the database
3) Use the knex connection object to execute queries
Writing queries using knex.raw
Multi-Line Queries
Dynamic Queries
A more complex example
Create, Update, and Delete
Challenges
client server database diagram
erd with one to many and many to many relationships