NodeJS MySQL Create Database

Create a MySQL database from Node.js with one query. The trick is connecting without naming a database first, running CREATE DATABASE IF NOT EXISTS, then verifying the schema exists. Here is how to do it with mysql2 and handle the failures you will hit in production.

The SQL you need

CREATE DATABASE [IF NOT EXISTS] database_name;

IF NOT EXISTS is the guard that makes the script idempotent. Without it, running the same code twice throws ER_DB_CREATE_EXISTS. Always include it for any script that runs on every deploy.

You can add a character set and collation:

CREATE DATABASE IF NOT EXISTS my_app_db
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

MySQL 8 and MariaDB 10 default to utf8mb4. Omit the charset clause unless you are matching a legacy import.

Set up the connection without a database name

The database does not exist yet, so createConnection must not include a database field. Adding one makes the driver try to select a schema that is not there, and you get ER_BAD_DB_ERROR: Unknown database.

const mysql = require('mysql2');

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: process.env.DB_PASSWORD
});

The connection attaches to the MySQL server itself, not to a specific schema. That is what lets you issue CREATE DATABASE before the target exists. For a walkthrough of connection setup and user privileges, see the Node.js MySQL connection guide.

Create the database with a callback

Send the SQL through connection.query. Check the error first, then confirm success.

const dbName = 'my_app_db';

connection.query(
  `CREATE DATABASE IF NOT EXISTS ${dbName}`,
  (err, result) => {
    if (err) {
      console.error(err.code, err.message);
      process.exit(1);
    }
    console.log(`Database ready: ${dbName}`);
  }
);

Never interpolate raw user input into that query. If the database name comes from a variable you do not control, validate it against an allowlist of [a-zA-Z0-9_] characters before building the SQL. A name like db; DROP TABLE users; would otherwise execute as a second statement.

Create the database with async/await

mysql2/promise wraps the same driver with promise-based methods. The SQL stays identical.

const mysql = require('mysql2/promise');

async function createDb() {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: process.env.DB_PASSWORD
  });

  await connection.query(
    `CREATE DATABASE IF NOT EXISTS my_app_db`
  );
  console.log('Database ready');

  await connection.end();
}

createDb().catch(err => {
  console.error(err.code, err.message);
  process.exit(1);
});

Pick one style for your codebase. Mixing callbacks and async/await in the same file makes the flow harder to follow.

Verify the database exists

CREATE DATABASE returning no error does not prove the schema is usable. Run SHOW DATABASES and check the list.

connection.query('SHOW DATABASES', (err, rows) => {
  if (err) {
    console.error(err.message);
    return;
  }

  const names = rows.map(r => r.Database);
  console.log('Databases:', names.join(', '));
  console.log('Created:', names.includes('my_app_db') ? 'yes' : 'no');
});

That check returns the ground truth from the server, not the assumption that the previous query worked.

Switch to the new database

Once the schema exists, you can work inside it two ways. Run USE on the same connection:

connection.query('USE my_app_db', (err) => {
  if (err) throw err;
  console.log('Now using my_app_db');
});

Or close the initial connection and open a new one with the database field set. This is the cleaner path for applications because every subsequent query targets the right schema without an extra USE.

const appConnection = mysql.createConnection({
  host: 'localhost',
  user: 'app_user',
  password: process.env.APP_DB_PASSWORD,
  database: 'my_app_db'
});

The USE statement is session-scoped. It only affects the current connection, not others.

Privileges and the errors they cause

Creating a database requires the CREATE privilege. Without it, the server rejects the statement even though the connection succeeds.

ErrorMeaningFix
ER_DB_CREATE_EXISTSDatabase name already takenAdd IF NOT EXISTS or handle the code
ER_ACCESS_DENIED_ERRORUser lacks CREATE privilegeGRANT CREATE ON *.* to the provisioning user
ER_BAD_DB_ERRORcreateConnection pointed at a missing databaseOmit the database field for the create step

Grant privileges explicitly rather than running your app as root:

-- Run this as a privileged user
GRANT ALL PRIVILEGES ON my_app_db.* TO 'app_user'@'localhost';
-- App user gets full access to one schema, no server-wide CREATE

Check what a user can do before debugging a failing create:

SHOW GRANTS FOR 'app_user'@'localhost';

Make deploys idempotent

Every deploy script that touches the database should be safe to run twice. IF NOT EXISTS is the first half of that. The second half is splitting privileges so the application user never needs CREATE at runtime.

The pattern: a setup script runs once during provisioning with a privileged user, creates the schema, then exits. Application code connects with a limited user and runs migrations or queries. If the schema already exists, the setup script is a no-op.

// setup.js - runs during deployment, not at request time
const mysql = require('mysql2/promise');

async function provision() {
  const admin = await mysql.createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_ADMIN_USER,
    password: process.env.DB_ADMIN_PASSWORD
  });

  await admin.query('CREATE DATABASE IF NOT EXISTS app_prod');
  await admin.query('CREATE TABLE IF NOT EXISTS app_prod.users (...)');
  await admin.end();

  console.log('Provisioning complete');
}

provision();

Run this with a CI tool or a manual deploy step, not inside the application request lifecycle. A compromised app user without the CREATE privilege cannot drop schemas it cannot create.

Quick reference

  • CREATE DATABASE IF NOT EXISTS name: creates once, no-ops on reruns
  • USE name: switches the current connection to that schema
  • SHOW DATABASES: lists every schema the user can see
  • SHOW GRANTS: lists privileges for the current user
  • mysql2/promise: use this for async/await, same SQL as the callback API

FAQ

These are the follow-up questions that come up after a first successful create.

mysql2

callback and promise

mysql

callback only

ER_DB_CREATE_EXISTS

CREATE DATABASE without IF NOT EXISTS on a name that already exists

ER_ACCESS_DENIED_ERROR

User lacks CREATE privilege

ER_BAD_DB_ERROR Unknown database

createConnection included database that does not exist yet

IF NOT EXISTS

Use for idempotent deploys

mysql vs mysql2

Use mysql2

CREATE vs USE

CREATE makes, USE selects

Aditya Gupta
Aditya Gupta

Aditya Gupta is a founding member and editor at CodeForGeek. He first found his way into tech by reading articles, and now writes approachable guides to Node.js security, authentication, AI tools, coding agents, and web scraping.

Articles: 529