Migrate a Ticket Schema

CloudflareBeginner
Practice Now

Introduction

Your ticket service must add urgency without losing existing requests. A schema rollout can fail if old rows cannot satisfy a new rule. You will create an ordered migration, test it locally, and apply the same file remotely while preserving ticket identities and subjects.

This lab starts independently with a supplied initial migration. It assumes the database creation and basic SQL from D01, but uses no previous VM or database.

Use your own learning account and a fresh VM. Setup first prepares Node.js 22.22.0, then runs npm install for project-local Wrangler 4.131.1 and any assessment dependencies under /home/labex/project/ticket-database. Direct dependency versions are pinned; the installation creates its own lockfile. No cloud login or assessed database work runs in setup. On a personal machine, install the same Wrangler version with npm install --save-dev wrangler@4.131.1 in your project.

This exercise uses small synthetic records within the D1 Free allowances. Existing account usage counts toward those allowances. No purchased domain is needed. Keep this VM until resource deletion and logout have both been checked.

Authorize this VM and select the account

In this step, you connect this fresh terminal to your own learning account. A Dashboard login alone does not authorize the VM. D1 permission allows database creation, SQL changes and deletion. Review the actual consent page, including Background Access, before authorizing.

Open the prepared project and inspect the pinned CLI:

cd /home/labex/project/ticket-database
npx wrangler --version

Expect 4.131.1. Start device authorization; --device displays a browser code, and --browser=false leaves the browser choice to you:

npx wrangler login --device --browser=false --scopes account:read user:read d1:write

Open the displayed URL in your browser, enter the current code, confirm your learning account and the permissions, and authorize. Wait for the terminal to confirm success. Never paste passwords or tokens into project files.

npx wrangler whoami --json

Check loggedIn: true, then read the account name and id, even when only one account is listed. Copy the intended ID into the configuration below. The following shell variable uses 6 random bytes (12 hexadecimal characters) to avoid colliding with other learners. A here-document writes the JSON between JSON lines; $RUN expands inside it.

The backslash before $schema keeps that JSON key literal; $RUN still expands to this run’s unique name.

RUN=labex-c04-d03-$(openssl rand -hex 6)
cat > wrangler.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "YOUR_ACCOUNT_ID",
  "main": "src/index.js",
  "compatibility_date": "2026-09-15",
  "workers_dev": true,
  "preview_urls": false
}
JSON

Replace YOUR_ACCOUNT_ID before running the block. Keep this terminal open so RUN remains available. name identifies this run; account_id selects the account for cloud operations. The file is ordinary JSON, which is also valid JSONC. No Worker is deployed by writing it.

Establish the existing ticket database

In this step, you prepare the existing version of the application database. A migration is a numbered SQL file describing a schema change. Wrangler records applied filenames in d1_migrations, so future runs can distinguish completed work from pending work. Setup supplies 0001_initial.sql as the old application version; you will create the upgrade yourself.

Create a disposable cloud database. --binding DB gives application code a short name, --update-config records its real name and UUID in wrangler.jsonc, and --use-remote=false keeps development local:

npx wrangler d1 create "$RUN-db" --binding DB --update-config --use-remote=false

Read the created name and ID, then inspect the saved binding:

cat wrangler.jsonc

The DB entry must name this run's database. A binding is a configured connection between code and a resource. Its UUID identifies the cloud database, while --local uses a separate SQLite database in this VM. Always include either --local or --remote in SQL commands.

Read the old schema before applying it:

cat migrations/0001_initial.sql

It has two existing tickets and no priority column. Apply it separately to local and remote targets, confirming this lab's database when prompted:

npx wrangler d1 migrations apply DB --local
npx wrangler d1 migrations apply DB --remote

Inspect rows and applied state:

npx wrangler d1 execute DB --remote --command "SELECT id, subject, status, source FROM tickets ORDER BY id; SELECT name FROM d1_migrations ORDER BY id;"

Both original tickets must exist; the applied migration is 0001_initial.sql. An applied migration is history: create a new file for later changes instead of editing that history.

Add a constrained priority locally

In this step, you give existing tickets a default priority without dropping the table. ALTER TABLE ... ADD COLUMN changes a table in place. A non-null added column needs a useful default for old rows. CHECK limits priorities to normal or urgent.

Create the next numbered migration:

npx wrangler d1 migrations create DB add_priority

In this fresh project it creates migrations/0002_add_priority.sql. Check that filename in the output. Write the change into that new file:

cat > migrations/0002_add_priority.sql <<'SQL'
ALTER TABLE tickets ADD COLUMN priority TEXT NOT NULL DEFAULT 'normal' CHECK(priority IN ('normal','urgent'));
SQL

List pending migrations, then apply only locally:

npx wrangler d1 migrations list DB --local
npx wrangler d1 migrations apply DB --local

Both existing tickets should acquire normal. Add an urgent ticket and query it:

npx wrangler d1 execute DB --local --command "INSERT INTO tickets (id, subject, source, priority) VALUES (3, 'Service unavailable', 'local', 'urgent'); SELECT id, subject, priority FROM tickets ORDER BY id;"

An out-of-range priority must fail rather than silently enter the table:

npx wrangler d1 execute DB --local --command "UPDATE tickets SET priority = 'critical' WHERE id = 3;"

Expect CHECK constraint failed; this intentional error leaves ticket 3 urgent. Read PRAGMA table_info(tickets) on the remote database to see that the cloud schema is still the old version:

npx wrangler d1 execute DB --remote --command "PRAGMA table_info(tickets);"

There is no remote priority yet. Local migration success does not update the cloud.

Apply the tested migration remotely

In this step, you roll out the same reviewed file to the remote database. Check pending work before confirming:

npx wrangler d1 migrations list DB --remote
npx wrangler d1 migrations apply DB --remote

Expect only 0002_add_priority.sql to be pending. The existing rows are kept. Add a remote urgent ticket, then inspect both data and migration history:

npx wrangler d1 execute DB --remote --command "INSERT INTO tickets (id, subject, source, priority) VALUES (3, 'Service unavailable', 'remote', 'urgent'); SELECT id, subject, priority FROM tickets ORDER BY id; SELECT name FROM d1_migrations ORDER BY id;"

Tickets 1 and 2 keep their subjects and have priority normal; ticket 3 is urgent. Both numbered filenames are recorded. Run apply once more:

npx wrangler d1 migrations apply DB --remote

It should report no pending migrations and leave the rows unchanged. This is why the history table matters: re-running the rollout does not re-run completed files. In Dashboard, open this run's D1 database and inspect its schema/table view to connect the new column with the CLI result. Do not edit the schema there.

The migrated priority column in D1 Studio

This example shows the original two tickets with the default normal priority and the new remote ticket with urgent priority. The generated database name identifies this example run; your name will differ. The SQL queries, migration history and constraint checks above establish the result; the screenshot is a visual reference.

Delete the disposable resources

In this step, you remove only this lab's resources while the VM is still authorized. Finish all functional checks first. Keep configuration until deletion verification is complete.

npx wrangler d1 delete DB

Inspect the prompt and confirm only this run's database. Then list databases:

npx wrangler d1 list --json

Your recorded database name and UUID must be absent from a successful response. Other resources may remain. An authentication or network error is inconclusive: resolve access and repeat the read before continuing. Run this step's verification while still logged in.

End this VM authorization

In this step, you end the authorization only after the independent deletion check passes. Logout removes this VM's stored Wrangler authorization; closing a VM alone is not cloud cleanup.

npx wrangler logout
npx wrangler whoami --json

Expect loggedIn: false. This unauthenticated query can exit nonzero; that is expected only when the structured response explicitly says you are logged out. Complete verification, then close the lab environment.

Summary

You practiced migrate a ticket schema. You checked observable database results, kept the selected account and local state explicit, and removed the disposable resources before logging out.