Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ironbyte/22a67056b0bcc77411b9f2e87b72dfed to your computer and use it in GitHub Desktop.
Save ironbyte/22a67056b0bcc77411b9f2e87b72dfed to your computer and use it in GitHub Desktop.
CRUD Knex Notes

Node.js Express JSON API - CRUD Stickers We'll be using:

Postgres for our database

knex.js for our database migrations, seeds and queries.

express.js for our JSON routes

Mocha, Chai and SuperTest to test our routes

Prerequisites (Mac OS Commands)

Latest version of Node.js brew install node Postgres brew install postgres brew services start postgres createdb Express Generator npm install -g express-generator Knex installed globaly npm install -g knex Server Check List Create a server folder

(example directory ~/dev/CRUD)

mkdir server then cd into server

Generate Express App

express initialize git repo

git init You also need a .gitignore file gitignore node In package.json edit: "name": "crud-stickers", "version: "1.0.0", Now we need to add and commit git add . git commit -m "initial commit" Create database

createdb name-this-database (this will hold stickers and all the things) Initialize knex project *Remain in the server directory

npm install --save knex pg *This should also install a package-lock.json and also add knex and pg dependencies in your package.json file

Install knex and pg

Create knexfile.js

Sets up the configurationg to connect knex to the database knex init In the knexfile.js delete everything except development. Should look like this: module.exports= { development: { client: 'pg', connection: 'postgres://localhost/named-database' }, }; Create sticker table migration

Watch video for creating the table sticker to paste in readme Now we can create the migration, this will create the migration folder and inside will be the migration: knex migrate:make create-sticker In the migrations folder: exports.up = function)knex, Promise) { return knex.schema.createTable('sticker', (table) => table.increments() table.text('title') table.text('description') table.float('rating') table.text('url') }) }

exports.down = function(knex, Promise) { return knex.schema.dropTable('sticker') } Run the migration knex migrate:latest Go into psql to make sure it worked psql named-database This will take you to the database. Now list it to see: \dt \d sticker Seed sticker table with sample data

Best practice to use numbers to begin your seed names because they run in alphabetical order. In larger projects you'll have multiple seeds and will want them to run in order. knex seed:make 01_sticker This created the seed folder and the seed file, in that file make the edits follwing. The idea behind seeding is that everytime you run the seed the database will go back to the same state exports.seed = function(knex, Promise) { return knex('sticker').del() .then(function () { return knex('sticker').insert(stickers) }) } In your stickers.js file, you want to make sure that your data is an object that looks like the table you created. Should have module.exports = at the top. In your stickers file, you need to pull that array of objects in. When we run the seed, it will drop the rows and then insert the array of stickers. It should look like: const stickers = require('../stickers') Then run: knex seed:run While in the database next to the named database you should be able to run: select * from sticker NOTE: \x will do an expanded display if you run select again. Convert Express App to JSON API

Remove view rendering

When an express app is generated by default, it comes with a "view" engine (jade / handlebars). Navigate into the app.js file and remove the view engine setup: // view engine setup app.set('views', path.join(__dirname, 'views')) app.set('view engine', 'jade') Delete "views" folder since it's not needed Remove routes folder

Delete the 'routes' folder because we will make our own In app.js remove these lines where the routes are used: var index = require('./routes/index') var users = require('./routes/users')

app.use('/', index) app.use('./users', users) Remove static serve and public folder

In the app.js file, remove line 19 or the line that says: app.use(express.static(path.join(__dirname, 'public'))) We delete this because it serves static files from the public folder. Delete the 'public' folder. Update error handler

At the bottom of app.js by default, it's trying to render the error page, instead we want to send back some json. Remove: `res.render('error') Add: res.json({ message: err.message, error: req.app.get('env') === 'development' ? err : {} }) Now you can remove the 2 lines above: res.locals.message = err.message res.locals.error = req.app.get('env') === 'development' ? err : {} Now whenever the server has an error it will send it back as json To verify everything is working run npm install to make sure it has the latest dependencies. If you are not using a view engine, uninstall it by npm uninstall jade. This removes it from package.json run npm start to make sure it starts It should be working if you go to localhost:3000 in the browser and see the message not found object since there are no more routes. Add api folder and create/mount router

This is for our crud routes and our stickers Inside the server folder, create a new folder named 'api' Inside of the api folder, create a new file named 'stickers.js' Inside of the stickers.js file we need to create an express router: const express = require('express')

const router = express.Router()

// This does a simple route to make sure its working router.get('/', (req, res) => { res.json({ message: 'working' }) })

module.exports = router Now we need to mount the router at this url '/api/v1/sticker' so every route inside of this file begins with this url. Inside app.js: // Change all of the cars to const from var express down to var app // Bring in stickers after const app = express() const stickers = require('./a[i/stickers') This is where we now mount the route. NOTE: It has to be exactly between: app.use(logger('dev')) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extedned: false}) app.use(cookieParser())

// PUT THE MOUNT HERE: app.use('/api/v1/stickers', stickers)

app.use(function(req, res, next) { const err -new Error('Not Found') err.status = 404 next(err) }) Now when you make a request from the mount, it's going to go into the router filer (sitckers.js) and runs the request handler Install nodemon as a development dependency npm install nodemon --save-dev FUN FACT: nodemon allows you to start up your server and watch it for files changes and restart it everytime a file changes In package.json add it in the "scripts" object under "start" like: "dev": "nodemon" In the terminal start up nodemon npm run dev If all is correct, in the browser you can add the stickers url we made. localhost:3000/api/v1/stickers and you should see the message we created Connect to the database'

This will require any module in our app and make queires against the database

Create database connection file

Create a folder in our server folder named 'db' and now create a file inside of db named 'knex.js' In the knex.js file we need to get the environment. This code, wherever it's running, will be connecting to a different database so when it connects on your machine, it will use the development connection. When you're on production their will be a different way to connect. const environment = process.env.NODE_ENV || 'development' Now add the knex config const config = (require('../knexfile') const environmentConfig = config[environment] const knex = require('knex') const connection = knex(environmentConfig) module.exports = connection Create a queries file

This will create one place where our queires can go In the DB folder, create a new file named 'queries.js'. Inside queries.js we need to require our connection to the db. So we require the knex file: const knex = require('./knex') // the connection

module.exports = { getAll(){ // this is a function that will get all the stickers from the sitcker table } } List all records with GET /api/v1/stickers

Create query

Create route

Now we will create a get route to list all of our stickers in our database Go into the api folder and go into stickers.js: const express = require('express')

const router = express.Router()

// require our queries const queries = require('../db/queries')

// This is the route where we want to list all of the stickers. The actual query to do that will be in another file so we need to bring that into here router.get('/', (req, res) => { queries.getAll().then(stickers => { res.json(stickers) }) })

module.exports = router In our queries.js file, we need to write out the getAll function const knex = require('./knex) //the connection!

module.exports = { getAll() { return knex('sticker') // this selects all the rows in the sticker table } } If we now enter localhost:3000/api/1/stickers We should get our data from our database and sending it back as json. Recap: queries.js creates the getAll method, the router file calls that method, we take the data through the promise and send it back as json Setup tests

Install mocha, chai and supertest

This command will save the tests as development dependencies because when the app is in production, we don't need the testing libraries.

npm install --save-dev mocha chai supertest

Add a test database connection

Head to knex.js file

module.exports= {
  development: {
    client: 'pg',
    connection: 'postgres://localhost/named-database'
  },
  development: {
    client: 'pg',
    connection: 'postgres://localhost/test- named-database'
  }
};
``` 

When we run these tests, they will run against a different database

Add npm test script

Create test directory and test file

In server folder mkdir test cd test touch app.test.js

In app.test.js:

describe('CRUD Stickers', () => {

}) In the package.json add "test": "NODE_ENV=test mocha" to the "scripts" object.

If you think back to the db connection we created in knex.js, we're using the test object in the knexfile.js file instead of the development connection because we utilized NODE_ENV in the knex.js file.

Drop/Create database

Before mocha runs, however, we want to drop the database and create it from scratch

So continue to add to this test value:

"test": "(dropdb --if-exists test-named-database && createdb test-named-database) && NODE_ENV=test mocha"

If we run dropdb --help we can use the option --if-exists because the first time you run it, it won't exist, so it won't try dropping something that doesn't exist.

Create before

In app.test.js:

// bring in our database connection const knex = require('')

describe('CRUD Stickers', (../db/knex) => { // this function will run before any of the test below it are defined. before(() => { // run migrations // run seeds }) }) Run migrations/seeds on test db In app.test.js: // bring in our database connection const knex = require('')

describe('CRUD Stickers', (../db/knex) => { // this function will run before any of the test below it are defined. before(() => { // run migrations knex.migrate.latest() .then(() => { // run seeds return knex.seed.run() })

}) }) Make sure the tests are working!

We need to tell mocha when the migration and seeds are done running using the built in done function we can call.

// bring in our database connection const knex = require('')

describe('CRUD Stickers', (../db/knex) => { // this function will run before any of the test below it are defined. // add built in mocha done function before((done) => { // run migrations knex.migrate.latest() .then(() => { // run seeds return knex.seed.run() //invoke done function }).then(() => done()) }) // have basic test to check if the before is creating the tables and seeding the databse

}) List all records with GET /api/v1/stickers Add test Show one record with GET /api/v1/stickers/:id Validate id Create query Create route Add test Create a record with POST /api/v1/stickers Create route Validate sticker! Create query Add test Update a record with PUT /api/v1/stickers/:id Create route Validate id Validate updates Create query Add test Delete a record with DELETE /api/v1/stickers/:id Create route Validate id Create query Add test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment