Database Basics: SQL vs. NoSQL
Before we write code, you need to know what kind of "storage room" you’re building. Databases generally fall into two camps:
-
Relational (SQL): Think of these like an Excel spreadsheet. Data is organized into tables with fixed columns and rows.
Examples: PostgreSQL, MySQL. -
Non-Relational (NoSQL): Think of these like a folder of JSON files. Data is stored in "documents," making it flexible and easy to scale.
Examples: MongoDB, Cassandra.
For this tutorial, we’ll use MongoDB because its JSON-like structure feels very natural for JavaScript developers.
Prerequisites
Before we write code, you need a database running.
- Install MongoDB: Download the Community Server and install it on your machine.
- Or Use the Cloud: Sign up for MongoDB Atlas (Free Tier) if you don't want to install anything locally.
- Check it's running: Open your terminal and type
mongosh(ormongo). If you see a prompt, you are ready.
Step 1: Setting Up Your Node.js Project
Before connecting to MongoDB, make sure you have a basic Node.js + Express server ready. If you haven’t set one up yet, follow my detailed guide here:
👉 Setting up a Node.js Server with Express ↗
Once your server is set up, navigate to your project directory and install the required dependencies. We’ll use Mongoose, an ODM (Object Data Modeling) library that makes working with MongoDB much easier, along with dotenv for environment variable management.
npm install mongoose dotenvAt this point, you should already have:
- A working Express server
- A
package.jsonfile - ES module support enabled (
type: "module")
With the base server in place, we’re ready to connect our application to MongoDB.
Recommended project structure:
node-db-intro/
│
├── src/
│ ├── config/
│ │ └── db.js # Database connection setup
│ ├── models/
│ │ └── user.model.js # Mongoose schema
│ ├── controllers/
│ │ └── user.controller.js # Business logic for user operations
│ ├── routes/
│ │ └── user.routes.js # API endpoints for user route
│ └── app.js # Express server
│
├── .env
├── .gitignore
├── server.js # Entry point
└── package.json
This structure:
- Separates concerns (routing, logic, models, config)
- Scales cleanly as your app grows
- Matches how real-world Node backends are organized
💡 Note: In this tutorial, we’ll define routes directly in app.js for simplicity, but the
controllers/androutes/folders are shown to demonstrate industry best practices for larger projects.
Step 2: Designing the Schema
In MongoDB, even though it's "schema-less," it's a best practice to define what your data looks like. Let’s create a simple User model.
Create: src/models/user.model.js
// models/user.model.js
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true }
}, { timestamps: true });
export default mongoose.model('User', userSchema);Step 3: Connecting to the Server
Now, let’s wire everything together. We’ll set up the database connection, create an Express server, and add a route to create users.
src/config/db.js - database connection setup.
// src/config/db.js
import mongoose from 'mongoose';
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('✅ Connected to MongoDB');
} catch (error) {
console.error('❌ MongoDB connection error:', error);
throw error;
}
};
export default connectDB;server.js - main entry file.
import app from "./src/app.js";
import connectDB from './src/config/db.js';
import dotenv from 'dotenv';
dotenv.config();
const PORT = process.env.PORT || 3000;
connectDB().then(() => {
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
}).catch((error) => {
console.error('Failed to connect to the database', error);
});src/app.js - Express app setup.
import express from 'express';
import User from './models/user.model.js';
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }))
// Routes
app.get('/', (req, res) => {
res.send('Welcome to the Node.js Database Intro!');
});
app.post('/users', async (req, res) => {
try {
const newUser = new User(req.body);
await newUser.save();
res.status(201).json(newUser);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
app.get('/users', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
app.delete('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.json({ message: 'User deleted' });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// Try it yourself: Create and implement the PUT and PATCH routes.
export default app;Step 4: Environment Variables
🔐 Pro Tip: Always use environment variables for your database URI to keep your credentials safe!
Create .env - environment variables file.
# --- For local development (No password needed usually) ---
PORT=3000
MONGO_URI=mongodb://localhost:27017/mydatabase
# --- Production Reference (Don't uncomment here) ---
# In production (Atlas), your URI will look like this:
# MONGO_URI=mongodb+srv://user:[email protected]/dbname
Note: Never commit this file to Git!
Step 5: Testing Your Setup
Start your MongoDB server (e.g., mongod command), verify it's running, and run your Node.js application:
node server.jsYou should see:
✅ Connected to MongoDB
Server running on http://localhost:3000
Send a POST request to http://localhost:3000/users with a JSON body:
{
"name": "Alice",
"email": "[email protected]"
}You can use tools like Postman or curl for this. If successful, you should get a response with the created user.
Expected Response:
{
"_id": "...",
"name": "Alice",
"email": "[email protected]",
"createdAt": "...",
"updatedAt": "...",
"__v": 0
}Then, try a GET request to http://localhost:3000/users to see all users in the database.
Why This Structure Matters
This layout:
- Makes your code easier to read, test, and scale
- Prepares your project for authentication, validation, and business logic
- Reflects how production Node.js backends are structured
In real-world systems, this naturally evolves into:
Routes → Controllers → Services → Models → Database
Troubleshooting Common Errors
MongooseServerSelectionError: connect ECONNREFUSED
- Cause: Your MongoDB server is not running.
- Fix: Open a new terminal and run
mongod(or start the service in your OS settings).
EADDRINUSE: address already in use :::3000
- Cause: You have another instance of your server running.
- Fix: Find the other terminal window and press
Ctrl + Cto stop it.
What's Next?
Congratulations! You’ve successfully connected a database to your Node.js application. Your app now has a memory.
But we have a problem.
Take a look at your app.js. It’s starting to look cluttered, isn't it? While we successfully put our Database Model in its own file, all of our Routing and Business Logic (the code that actually saves and finds users) is shoved directly into the server configuration file. If we add a few more features, this file will become a nightmare to read and maintain.
In the next post, we will clean this up. We’ll move from "Spaghetti Code" to a professional Controller-Route architecture, separating the "Waiters" (Routes) from the "Chefs" (Controllers).
Happy Coding!