An API without a database is just a temporary calculator. An API with a database is a persistent business. In this lesson, you will learn how to connect your Express server to a persistent database to safely create and read data.
1The Secure Middleman
So far, our Express server has only returned hardcoded JavaScript objects. If the Node.js server process restarts, all that data vanishes instantly because it only lived in temporary RAM. To build a production application, your API must connect to a Persistent Database (like PostgreSQL, MySQL, or MongoDB).
Why not just connect the React frontend directly to the database? Security. The frontend code is completely public. If you embedded your database password in your React code, anyone could extract it and wipe your entire database.
Instead, your Express API acts as the secure middleman. It runs on a private, secure server. It holds the database passwords in hidden environment files. When a client requests data, the API authenticates the user, safely queries the database on their behalf, and returns only the data they are authorized to see.
async function execute() {
// See concept above
}
2The Rise of ORMs
Historically, backend developers wrote raw SQL string queries directly inside their Node.js code (e.g., db.query('SELECT * FROM users')). This practice is widely discouraged today because it is extremely vulnerable to SQL Injection—a devastating hack where malicious users type raw SQL commands into login forms to destroy your database.
Modern enterprise backends use an ORM (Object-Relational Mapper) like Prisma, Sequelize, or TypeORM. An ORM allows you to interact with your database using secure, standardized JavaScript methods instead of raw SQL strings.
// Instead of dangerous raw SQL:
// db.query("SELECT * FROM users WHERE id = " + req.params.id);
// You use a safe ORM method:
const user = await prisma.user.findUnique({
where: { id: req.params.id }
});The ORM takes your clean JavaScript code, automatically sanitizes any malicious user input, translates it into highly optimized SQL, and executes it securely.
async function execute() {
// See concept above
}
3Databases are Slow (Async/Await)
When you execute an ORM command inside an Express route, your API has to send a network request to the database server (which might be physically located in a different data center), wait for the database to search its hard drives, and wait for the data to travel back.
Because of this delay, database operations are Asynchronous. You MUST use the await keyword before every single ORM call, and your route callback must be labeled async.
app.get('/users/:id', async (req, res) => {
// 1. AWAIT the slow database query
const user = await prisma.user.findUnique({
where: { id: parseInt(req.params.id) }
});
// 2. ONLY proceed once the data has returned
res.json(user);
});If you forget the await keyword, Node.js will not wait. It will instantly execute res.json(), returning a blank 'Promise' object to the client instead of the actual user data.
async function execute() {
// See concept above
}
4Executing Create & Read
We now combine our routing knowledge with our ORM.
To Read data (a GET request), you extract the target ID from req.params, pass it into the ORM's find method, await the result, and return it via res.json().
To Create data (a POST request), you extract the incoming JSON payload from req.body, pass it into the ORM's create method, await the insertion, and return the newly generated database record along with a 201 Created HTTP status code.
app.post('/users', async (req, res) => {
const newData = req.body;
const newUser = await prisma.user.create({ data: newData });
res.status(201).json(newUser);
});async function execute() {
// See concept above
}
