New to Rust? Grab our free Rust for Beginners eBook Get it free →
Connect Deno to MySQL with mysql2

Deno can use the mysql2 driver through its npm compatibility layer, so a Deno app can open a MySQL connection without reviving an older URL import. The working path is small: give the app a limited database account, read its connection values from environment variables, execute a parameterized query, then close the pool.
I ran the example below against a temporary MariaDB server using Deno 2.9.6 and mysql2. MariaDB speaks the MySQL protocol for this query, but use the MySQL server and account policy that match your deployment.
Prepare a database and limited account
A database connection needs a server, a database name, and an account with the permissions your query needs. Do not use the root account in an application. MySQL’s account-management documentation is the reference for creating users and grants.
Run these statements as an administrator on a development server. The account can read the tutorial table and nothing else.
CREATE DATABASE tutorial_db;
CREATE USER 'tutorial'@'localhost' IDENTIFIED BY 'choose-a-password';
GRANT SELECT ON tutorial_db.* TO 'tutorial'@'localhost';
USE tutorial_db;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL
);
INSERT INTO users (name) VALUES ('Shahid'), ('Maya');
The GRANT statement is a boundary, not a formality. If your app inserts or updates rows, add only those operations after checking which queries the app sends.
Create the Deno connection
Deno’s MySQL2 example uses mysql2 from npm. A pool is useful when a server handles more than one request because it can reuse connections instead of opening one for every query.
Save this as app.ts. The program refuses to hard-code a password because the values come from the environment. The query accepts an id value separately from the SQL text, which prevents a value from becoming executable SQL.
import mysql from "npm:mysql2/promise";
const pool = mysql.createPool({
host: Deno.env.get("DB_HOST") ?? "127.0.0.1",
port: Number(Deno.env.get("DB_PORT") ?? "3306"),
user: Deno.env.get("DB_USER"),
password: Deno.env.get("DB_PASSWORD"),
database: Deno.env.get("DB_NAME"),
waitForConnections: true,
connectionLimit: 5,
});
const [rows] = await pool.execute<mysql.RowDataPacket[]>(
"SELECT id, name FROM users WHERE id >= ? ORDER BY id",
[1],
);
console.log(rows);
await pool.end();
Why execute() takes two arguments
The first argument is the SQL statement and the second supplies its values. mysql2 sends the value through its parameter mechanism, so the database reads it as data rather than as part of the statement.
Do not build the WHERE clause with a template string. A placeholder handles values. Table names and sort directions need an allowlist because placeholders do not represent SQL identifiers.
Run the query with Deno permissions
Deno requires permission to read environment variables and open a network connection. Set the values in your shell or deployment configuration, then run this command from the folder containing app.ts.
DB_HOST=127.0.0.1 \
DB_PORT=3306 \
DB_USER=tutorial \
DB_PASSWORD=choose-a-password \
DB_NAME=tutorial_db \
deno run --allow-env --allow-net app.ts
The terminal result shows the two rows inserted during setup. The same command completed with exit status 0 in the local run.

Handle the connection boundary
Use a local loopback address only when the database runs on the same machine. A managed database needs its host, port, account, and transport settings from that provider. MySQL documents encrypted connections separately, so do not assume a production server accepts the same connection settings as a local development instance.
Connection refused
A connection-refused error usually means no MySQL server listens at DB_HOST and DB_PORT. Check the server service first, then compare the host and port with the values configured for your database.
Access denied
An access-denied error means the server received the request but rejected the account or its host rule. Confirm the username, password, database name, and the host portion of the MySQL account before adding broader privileges.
Use a pool when the process stays alive
For a one-off script, calling pool.end() releases its connections before the process exits. Keep one pool for the lifetime of a web server, then close it during graceful shutdown. Creating a new pool inside every request removes the reuse that pooling provides.
Change the SELECT condition to the row your application needs, keep the value in the parameter array, and grant the account only the operation that query requires.




