Persist data with PostgreSQL or MongoDB
A database preserves records beyond a Node process restart. PostgreSQL organizes relational tables with constraints and joins. MongoDB stores documents in collections. Choose according to your data model and operational requirements; a course project does not need both at once. In either case, the server validates input, uses a restricted database identity and keeps connection credentials outside source control.
PostgreSQL: schema and parameterized values
Install pg with npm install pg. Create a local practice database, configure DATABASE_URL in your terminal's environment, and run this schema once using your PostgreSQL client.
CREATE TABLE courses (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title varchar(120) NOT NULL CHECK (length(trim(title)) > 0)
);
Save postgres.mjs. The environment value must point to your practice database. Follow your hosting provider's TLS instructions for remote connections; do not disable certificate checking as a shortcut.
import pg from 'pg';
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is required');
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
pool.on('error', error => console.error('Idle database connection failed:', error.code));
try {
const title = 'Node Foundations';
const result = await pool.query(
'INSERT INTO courses(title) VALUES ($1) RETURNING id, title',
[title]
);
console.log(result.rows[0]);
} finally {
await pool.end();
}
Run node postgres.mjs. It prints the inserted course with a generated ID. Each run inserts another row. $1 binds a value; it does not substitute table or column names. Never concatenate visitor input into SQL. For a long-running API, create one shared pool and close it on shutdown rather than ending it after each request.
Transactions use the same checked-out client
For related writes, acquire a client using pool.connect(), run BEGIN, all statements and COMMIT on that client, then release it in finally. On failure, ROLLBACK before release. Separate pool.query calls are not a safe transaction because they can use different connections. Add constraints to enforce rules even when application validation is bypassed.
MongoDB: select fields and close a script connection
As an alternative, install mongodb and set MONGODB_URI for a local practice server. Save mongo.mjs:
import { MongoClient } from 'mongodb';
if (!process.env.MONGODB_URI) throw new Error('MONGODB_URI is required');
const client = new MongoClient(process.env.MONGODB_URI);
try {
await client.connect();
const courses = client.db('learning_practice').collection('courses');
const result = await courses.insertOne({ title: 'Node Foundations' });
const saved = await courses.findOne(
{ _id: result.insertedId }, { projection: { title: 1 } }
);
console.log(saved);
} finally {
await client.close();
}
A web service normally reuses its client instead of connecting per request. Validate any external ObjectId string before conversion. Build query objects from allowed fields; never pass req.body directly as a database filter or update document. Add indexes based on real query patterns and explicit uniqueness requirements.
Practice checkpoint
Choose one database and add a read operation after restarting the script. Confirm persistence. Send a title containing an apostrophe through a parameterized query and verify it remains data. Plan migrations, backups and connection limits before deployment. Do not print connection strings in errors or API responses.