Connecting Your App to a Database
Once a database is installed and running on your Ubuntu server, your application still needs a way to talk to it. That conversation happens over a network socket, and it relies on three things working together: a connection string (the address and login details), a driver (a library that speaks the database’s protocol), and a connection pool (a set of reused connections so you are not opening a new one for every request). Getting these right — and keeping your passwords out of your source code — is one of the most important parts of running an app in production.
What a connection string is
A connection string is a single line of text that tells your app everything it needs to find and log in to a database: the protocol, username, password, host (server address), port (a numbered door on that server), and the database name. Most databases use a URL-style format so one string carries all of it.
The general shape looks like this:
protocol://username:password@host:port/database_name?option=value
Here is how that breaks down for the common databases you will run on Ubuntu:
# PostgreSQL (default port 5432)
postgres://appuser:[email protected]:5432/myapp
# MySQL / MariaDB (default port 3306)
mysql://appuser:[email protected]:3306/myapp
# MongoDB (default port 27017)
mongodb://appuser:[email protected]:27017/myapp?authSource=admin
# Redis (default port 6379, often password-only, no username)
redis://:[email protected]:6379/0
Security gotcha: A connection string contains a plaintext password. Never paste it into a chat, a screenshot, a Git commit, or a log line. If a connection string leaks, treat the password as compromised and rotate it immediately.
What a driver is
A driver is a library you install into your application that knows how to speak a specific database’s wire protocol (the low-level language the database server understands). Your code talks to the driver in a friendly way; the driver translates that into bytes the database accepts and turns the response back into objects your code can use.
You install drivers with your language’s package manager, not with apt. A few common ones:
# Node.js
npm install pg # PostgreSQL
npm install mysql2 # MySQL / MariaDB
npm install mongodb # MongoDB
npm install redis # Redis
# Python
pip install psycopg2-binary # PostgreSQL
pip install PyMySQL # MySQL
pip install pymongo # MongoDB
pip install redis # Redis
| Database | Node.js driver | Python driver | Default port |
|---|---|---|---|
| PostgreSQL | pg | psycopg2 | 5432 |
| MySQL/MariaDB | mysql2 | PyMySQL | 3306 |
| MongoDB | mongodb | pymongo | 27017 |
| Redis | redis | redis | 6379 |
What connection pooling is
Opening a fresh connection to a database is slow: there is a network handshake, an authentication step, and memory set aside on the server. If your app opened a new connection for every web request, it would waste time and quickly exhaust the database’s connection limit (PostgreSQL defaults to 100).
A connection pool solves this. The pool opens a small number of connections once, keeps them alive, and hands a free one to each request. When the request finishes, the connection goes back into the pool instead of closing. This is faster and protects the database from being overwhelmed.
When to use a pool: almost always for a web app or API that handles many requests. When NOT to: a short-lived script or a one-off migration that runs once and exits — a single connection is simpler there.
Here is a real Node.js example using pg with a pool:
node app.js
// app.js
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // up to 10 connections in the pool
idleTimeoutMillis: 30000, // close idle connections after 30s
});
async function main() {
const result = await pool.query('SELECT NOW() AS now');
console.log('Connected. Server time:', result.rows[0].now);
await pool.end();
}
main().catch((err) => {
console.error('DB error:', err.message);
process.exit(1);
});
Output:
Connected. Server time: 2026-06-15T10:42:08.512Z
Notice the code never contains a password. It reads process.env.DATABASE_URL — an environment variable — instead.
Keep credentials in environment variables, not code
The single most important rule: your database password must never live in your source code. If it is in your code, it ends up in Git history, in backups, and visible to anyone with repository access. Instead, store it in an environment variable (a value the operating system hands to your process at startup) and read it at runtime.
For local development, a .env file is common. Create it and make sure it is ignored by Git:
echo "DATABASE_URL=postgres://appuser:s3cret@localhost:5432/myapp" > .env
echo ".env" >> .gitignore
For a real Ubuntu server, the cleanest approach is to put the variable in the systemd service that runs your app (systemd is the standard tool that starts and manages services on Ubuntu). Use a separate file with locked-down permissions so other users cannot read it:
sudo install -m 600 /dev/null /etc/myapp/db.env
sudo nano /etc/myapp/db.env
Put the variable inside that file:
DATABASE_URL=postgres://appuser:s3cret@localhost:5432/myapp
Then load it from your service unit at /etc/systemd/system/myapp.service:
[Service]
EnvironmentFile=/etc/myapp/db.env
ExecStart=/usr/bin/node /opt/myapp/app.js
User=myapp
Reload systemd and restart so the change takes effect:
sudo systemctl daemon-reload
sudo systemctl restart myapp
Tip: The
-m 600on the env file means only the owner (root) can read it. This keeps the password off the screen and out of reach of other accounts on the box. For managed platforms, use their secrets store instead of a file — see the deployment and secrets pages.
Best Practices
- Build connection strings from environment variables; never hard-code credentials in source files.
- Add
.envand any*.envfile to.gitignorebefore your first commit. - Use a connection pool for any long-running web app, and set a sensible
maxso you stay under the database’s connection limit. - Give each app its own database user with only the privileges it needs, not a superuser account.
- Require TLS (encrypted connections) when the database is on a different host — add
?sslmode=requirefor PostgreSQL. - Lock down env files with
chmod 600and run the app as a dedicated, non-root user. - Rotate the password immediately if a connection string is ever exposed.