Knex

Knex est un query-builder : il permet de réaliser des requêtes vers une base de données en Javascript de façon normalisée et sûre.

  • knex abstrait les petites différences entre les dialectes SQL
  • knex “sanitize” les requêtes, protégeant contre les attaques par injection SQL
  • knex fournit un service de migration pour gérer les évolutions de la structure des données d’un projet

Installation

cd project
npm init
npm i knex sqlite3 pg

Initialisation

npx knex init

Créé un fichier de configuration knexfile.js. Par défaut il fait référence à une base de données sqlite3 ; il faut l’éditer pour choisir un autre SGBD, un autre nom de base de données, etc.

Migrations

Knex a un système de migrations complet qui permet de gérer :

  • la création initiale des tables et de leur structure
  • la gestion des modifications ultérieures de structure
  • la gestion des ajouts de données (seed)

Migration initiale de création des tables

npx knex migrate:make migration_initiale

Un répertoire migrations est créé contenant un fichier <date>_migration_initiale.js. Il faut l’éditer avec la description de la structure des tables :

exports.up = function(knex) {

   return knex.schema
      .createTable('users', table => {
         table.increments('id').unsigned().primary()
         table.string('email').unique().notNullable()
         table.string('password')
         table.timestamp('created_at').defaultTo(knex.fn.now())
         table.string('fullname').notNull()
      })
      .createTable('pictures', table => {
         table.increments('id').unsigned().primary()
         table.integer('user_id').references('id').inTable('users').notNull().onDelete('cascade')
         table.timestamp('created_at').defaultTo(knex.fn.now())
         table.string('path').notNull()
      })
}

exports.down = function(knex) {
   return knex.schema
      .dropTable('users')
      .dropTable('pictures')
}

La partie up doit réaliser la mise à jour souhaitée (ici la création de deux tables) ; la partie down doit défaire ce qu’à fait la partie up, au cas ou on souhaiterait ‘défaire’ une migration.

Application de la migration :

npx knex migrate:latest

Un fichier de base de données sqlite3 est créé dans le répertoire courant, avec les deux tables users et pictures.

Ajout d’une migration de structure

On souhaite ajouter une colonne à users et un index à pictures :

npx knex migrate:make add_role_index_path

Un fichier vide migrations/<date>_add_role_index_path.js est créé, qu’il faut éditer :

exports.up = function(knex) {

   return knex.schema
      .table('users', table => {
         table.enum('role', ['user', 'admin']).notNull()
      })
      .table('pictures', table => {
         table.index([ 'path' ])
      })
}

exports.down = function(knex) {
   return knex.schema
      .table('users', table => {
         table.dropColumn('role')
      })
      .table('pictures', table => {
         table.dropIndex([ 'path' ])
      })
}

Application de la migration :

npx knex migrate:latest

Accès programmatique à la base de données avec knex

Pour un accès à la base de données en javascript, à un niveau proche du SQL.

const knex = require('knex')
const config = require('./knexfile.js')
const database = knex(config.development)

async function main() {
   const [{ id: jcId }] = await database
      .into('users')
      .insert({email: 'jc@n7.fr', fullname: "JCB", role: 'admin'}, 'id')
   console.log('jcId', jcId)

   await database
      .into('users')
      .update({email: "jc@mail.fr"})
      .where({id: jcId})

   const [user] = await database
      .select('*')
      .from('users')
      .where({id: jcId})
   console.log('user', user)

   process.exit(0)
}

main()

Sanitizing

knex.raw('select * from foo where id = ?', [1]) // OK
knex.raw(`select * from foo where id = ${id}`)  // Danger ! Injection SQL possible

Cheat sheet

Knex cheat sheet