New to Rust? Grab our free Rust for Beginners eBook Get it free →
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.
| Error | Meaning | Fix |
|---|---|---|
| ER_DB_CREATE_EXISTS | Database name already taken | Add IF NOT EXISTS or handle the code |
| ER_ACCESS_DENIED_ERROR | User lacks CREATE privilege | GRANT CREATE ON *.* to the provisioning user |
| ER_BAD_DB_ERROR | createConnection pointed at a missing database | Omit 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 rerunsUSE name: switches the current connection to that schemaSHOW DATABASES: lists every schema the user can seeSHOW GRANTS: lists privileges for the current usermysql2/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




