mevn-orm

Mevn ORM

npm GitHub license GitHub issues

Mevn ORM is a small ActiveRecord-style ORM built on top of Knex.

It exports:

Status

This project is in maintenance mode. Core functionality works, but new features are limited.

Requirements

Installation

npm install mevn-orm knex
npm install mysql2

For SQLite development/testing:

npm install sqlite3
# or:
npm install better-sqlite3

Quick Start

1) Configure Mevn ORM

import { configureDatabase } from 'mevn-orm'

configureDatabase({
  client: 'sqlite3',
  connection: {
    filename: './dev.sqlite'
  }
})

Supported clients (canonical Knex client names):

Connection styles:

2) Define a model

import { Model } from 'mevn-orm'

class User extends Model {
  override fillable = ['name', 'email', 'password']
  override hidden = ['password']
}

3) Use it

const created = await User.create({
  name: 'Jane Doe',
  email: 'jane@example.com',
  password: 'hash-me-first'
})

const found = await User.find(created.id as number)
await found?.update({ name: 'Jane Updated' })

API Reference

Exports

Using DB directly

You can always drop down to Knex after configuration:

import { configureDatabase, DB } from 'mevn-orm'

configureDatabase({
  client: 'sqlite3',
  connection: {
    filename: './dev.sqlite'
  }
})

const users = await DB('users').select('*')

More Configuration Examples

import { configureDatabase } from 'mevn-orm'

// MySQL / mysql2
configureDatabase({
  client: 'mysql2',
  connection: {
    host: '127.0.0.1',
    port: 3306,
    user: 'root',
    password: 'secret',
    database: 'app_db'
  }
})

// Postgres (connection string)
configureDatabase({
  client: 'pg',
  connection: process.env.DATABASE_URL
})

// MSSQL
configureDatabase({
  client: 'mssql',
  connection: {
    host: '127.0.0.1',
    user: 'sa',
    password: 'StrongPassword!',
    database: 'app_db',
    // MSSQL driver options live under `options` (passed through to tedious)
    options: {
      encrypt: true
    }
  }
})

Nuxt/Nitro Example

Use a server plugin to initialise the ORM once at Nitro startup. You can also run idempotent migrations (and optional rollback) during boot.

Because Nitro bundles server code, migration files must be copied into the build output.

nuxt.config.ts:

import { cp } from 'node:fs/promises'

export default defineNuxtConfig({
  nitro: {
    hooks: {
      compiled: async () => {
        await cp('server/assets/migrations', '.output/server/assets/migrations', {
          recursive: true
        })
      }
    }
  }
})

server/plugins/mevn-orm.ts:

import { defineNitroPlugin } from 'nitropack/runtime'
import { existsSync } from 'node:fs'
import {
  configureDatabase,
  setMigrationConfig,
  migrateLatest,
  migrateRollback
} from 'mevn-orm'

const isIgnorableMigrationError = (error: unknown): boolean => {
  const message =
    error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()

  return (
    message.includes('already exists') ||
    message.includes('duplicate') ||
    message.includes('does not exist') ||
    message.includes('no such table') ||
    message.includes('the migration directory is corrupt')
  )
}

export default defineNitroPlugin(async () => {
  configureDatabase({
    client: 'pg',
    connection: process.env.DATABASE_URL
  })

  // Nitro runtime path differs in dev vs built server.
  const migrationDirectory = existsSync('./.output/server/assets/migrations')
    ? './.output/server/assets/migrations'
    : './server/assets/migrations'

  setMigrationConfig({
    directory: migrationDirectory,
    extension: 'ts'
  })

  // Idempotent at boot: if already migrated, Knex returns empty log.
  try {
    await migrateLatest()
  } catch (error) {
    if (!isIgnorableMigrationError(error)) throw error
  }

  // Optional rollback at boot (usually only for dev/preview).
  if (process.env.NITRO_ROLLBACK_ON_BOOT === 'true') {
    try {
      await migrateRollback(undefined, false)
    } catch (error) {
      if (!isIgnorableMigrationError(error)) throw error
    }
  }
})

Migrations

Migrations are programmatic and use Knex’s migration API under the hood.

import {
  configureDatabase,
  setMigrationConfig,
  makeMigration,
  migrateLatest,
  migrateRollback,
  migrateList
} from 'mevn-orm'

configureDatabase({
  client: 'sqlite3',
  connection: { filename: './dev.sqlite' }
})

setMigrationConfig({
  directory: './migrations',
  extension: 'ts'
})

await makeMigration('create_users_table')
await migrateLatest()
const { completed, pending } = await migrateList()
await migrateRollback(undefined, false) // rollback last batch

Repository migration commands (no knexfile required):

pnpm run migrate
pnpm run migrate:make -- create_users_table
pnpm run migrate:rollback
pnpm run migrate:list
pnpm run migrate:version

Security Notes