Skip to main content
Version: 1.2.1

Migrations & Table Creation

OrvexORM can create your tables automatically from your models, or let you write migrations to manage the evolution of your database over time.

Quick creation with sync()

The simplest method: you define your model and call .sync(). The table is automatically created if it doesn't exist.

local User = OrvexORM.model("users", {
identifier = "string",
money = "number",
job = "string",
}, { primaryKey = "identifier" })

-- Creates the "users" table if it doesn't exist
User.sync()

:::tip When to use sync()? sync() is perfect for development and initial testing. It creates the table if it doesn't exist, but doesn't modify it if it already exists. For structural changes, use migrations. :::

Type mapping

When you use sync(), OrvexORM converts your schema types to MySQL types:

OrvexORM typeMySQL type
"string"VARCHAR(255)
"number"INT
"boolean"TINYINT(1)
"json"JSON
"auto"TEXT

The primary key is automatically configured as NOT NULL. If it's a "number" field, it's also AUTO_INCREMENT.

Migrations

Migrations let you version your database changes. Each migration has a unique name, an up function (apply) and a down function (revert).

Defining migrations

OrvexORM.migrations({
{
name = "001_create_users",
up = function(schema)
schema:create("users", function(t)
t:primaryString("identifier", 60)
t:integer("money")
t:string("job")
t:json("position")
t:timestamp("created_at")
end)
end,
down = function(schema)
schema:drop("users")
end,
},
{
name = "002_create_vehicles",
up = function(schema)
schema:create("vehicles", function(t)
t:id()
t:string("owner", 60)
t:string("plate", 10)
t:string("model")
t:integer("fuel")
t:json("mods")
end)
end,
down = function(schema)
schema:drop("vehicles")
end,
},
})

Applying migrations

local count = OrvexORM.migrate()
print(count .. " migration(s) applied")

OrvexORM keeps track of already applied migrations in an orvex_migrations table. Previously executed migrations are never replayed.

Rolling back migrations

local count = OrvexORM.rollback()
print(count .. " migration(s) rolled back")

rollback() reverts the last batch of migrations (the last migrate() call).

The Schema Builder

In migrations, you have access to a Schema Builder to define your tables in a readable way.

Creating a table

schema:create("players", function(t)
t:id() -- INT UNSIGNED NOT NULL AUTO_INCREMENT (PK)
t:string("name", 100) -- VARCHAR(100)
t:integer("money") -- INT
t:bigInteger("experience") -- BIGINT
t:float("rating") -- FLOAT
t:double("coordinates") -- DOUBLE
t:boolean("vip") -- TINYINT(1)
t:text("bio") -- TEXT
t:json("inventory") -- JSON
t:datetime("last_login") -- DATETIME
t:timestamp("created_at") -- TIMESTAMP
end)

Available column types

MethodMySQL typeDescription
t:id(name?)INT UNSIGNED AUTO_INCREMENTAuto-increment primary key (default: "id")
t:primaryString(name, length?)VARCHAR(n) NOT NULLText primary key (default: 60 characters)
t:string(name, length?)VARCHAR(n)Text (default: 255 characters)
t:integer(name)INTInteger
t:bigInteger(name)BIGINTLarge integer
t:float(name)FLOATDecimal number (single precision)
t:double(name)DOUBLEDecimal number (double precision)
t:boolean(name)TINYINT(1)True/false
t:text(name)TEXTLong text
t:json(name)JSONJSON data
t:datetime(name)DATETIMEDate and time
t:timestamp(name)TIMESTAMPTimestamp (auto CURRENT_TIMESTAMP)

Dropping a table

schema:drop("old_table")

-- Or in "safe" mode (does nothing if the table doesn't exist)
schema:dropIfExists("maybe_table")

Modifying an existing table

schema:table("users", function(t)
-- Add columns
t:string("email", 255)
t:boolean("verified")

-- Drop a column
t:dropColumn("old_column")

-- Rename a column
t:renameColumn("name", "full_name")
end)

Raw SQL

If you need a special operation, you can add raw SQL:

schema:raw("ALTER TABLE users ADD INDEX idx_job (job)")

Full examples

Typical FiveM server

OrvexORM.migrations({
{
name = "001_create_users",
up = function(schema)
schema:create("users", function(t)
t:primaryString("identifier", 60)
t:integer("money")
t:string("job")
t:json("position")
t:json("inventory")
t:timestamp("created_at")
end)
end,
down = function(schema)
schema:drop("users")
end,
},
{
name = "002_create_vehicles",
up = function(schema)
schema:create("vehicles", function(t)
t:id()
t:string("owner", 60)
t:string("plate", 10)
t:string("model")
t:integer("fuel")
t:json("mods")
end)
end,
down = function(schema)
schema:drop("vehicles")
end,
},
{
name = "003_create_roles",
up = function(schema)
schema:create("roles", function(t)
t:id()
t:string("name", 50)
end)
schema:create("user_roles", function(t)
t:id()
t:string("user_id", 60)
t:integer("role_id")
end)
end,
down = function(schema)
schema:drop("user_roles")
schema:drop("roles")
end,
},
})

-- On server startup
OrvexORM.migrate()

Adding a column later

OrvexORM.migrations({
-- ... previous migrations ...
{
name = "004_add_email_to_users",
up = function(schema)
schema:table("users", function(t)
t:string("email", 255)
end)
end,
down = function(schema)
schema:table("users", function(t)
t:dropColumn("email")
end)
end,
},
})

OrvexORM.migrate() -- Only migration 004 is executed

How it works internally

  1. OrvexORM creates an orvex_migrations table to track applied migrations
  2. When you call ORM.migrate(), it compares your migrations with those already applied
  3. New migrations are executed in order
  4. Each batch of migrations receives a "batch" number (useful for rollback)
  5. ORM.rollback() only reverts the last batch

:::caution In production Migrations modify your database structure. Always make a backup before applying migrations in production. And test your migrations on a development server first! :::