Blogs

Level Up Your Node.js App with Express.js (Part 2)

04/07/25

So you built your first Node.js app?

Nice. You've got the basic server, you're handling GET and POST requests, and you’re hyped.
But let’s be real — writing everything from scratch with the native http module gets messy real quick.

That’s where Express.js enters the chat.


Wait, what even is Express? 🤔

Express.js is a minimalist and flexible Node.js web application framework
that provides a robust set of features for building web and mobile applications.

Translation? It handles all the annoying parts for you — routing, parsing JSON, handling errors, and more — so you can focus on building.


Step 1: Setup the Project (Express-style) 🛠️

If you’ve got your notes-api folder already, you can work from there.
Or create a fresh one if you want a clean start:

mkdir express-notes-api
cd express-notes-api
npm init -y
npm install express

Create your main file:

touch index.js

Step 2: Create Your First Express App 💡

Inside index.js:

const express = require("express");
const app = express();

app.use(express.json()); // Middleware to parse JSON bodies

// Sample data
let notes = [
  { id: 1, text: "Learn Node.js" },
  { id: 2, text: "Move to Express.js" },
];

// GET all notes
app.get("/notes", (req, res) => {
  res.json(notes);
});

// POST a new note
app.post("/notes", (req, res) => {
  const newNote = {
    id: notes.length + 1,
    text: req.body.text,
  };
  notes.push(newNote);
  res.status(201).json(newNote);
});

// Server listener
app.listen(3000, () => {
  console.log("🚀 Server running at http://localhost:3000");
});

Now run:

node index.js

And boom — you’ve got a fully working REST API in like 15 lines.


Step 3: Refactor into Routes 🧱

Right now, everything lives in index.js, which is fine for demos but not for real projects.

Let’s split the routes out.

Folder structure:

express-notes-api/
├── routes/
   └── notes.js
├── index.js
├── package.json

routes/notes.js

const express = require("express");
const router = express.Router();

let notes = [
  { id: 1, text: "Learn Node.js" },
  { id: 2, text: "Move to Express.js" },
];

router.get("/", (req, res) => {
  res.json(notes);
});

router.post("/", (req, res) => {
  const newNote = {
    id: notes.length + 1,
    text: req.body.text,
  };
  notes.push(newNote);
  res.status(201).json(newNote);
});

module.exports = router;

Update index.js:

const express = require("express");
const app = express();

app.use(express.json());
app.use("/notes", require("./routes/notes"));

app.listen(3000, () => {
  console.log("🚀 Server running at http://localhost:3000");
});

Congrats — you’ve officially modularized your API 🔥


Step 4: Add Error Handling 🧯

Let’s make your app a bit more robust:

// In routes/notes.js

router.get("/:id", (req, res) => {
  const note = notes.find(n => n.id === parseInt(req.params.id));
  if (!note) return res.status(404).json({ error: "Note not found" });
  res.json(note);
});

Now you can GET /notes/1 or /notes/2 — and gracefully handle not-found cases.


Step 5: Use Middleware (like a boss) ⚙️

Middleware are functions that run before your route handlers. For example:

// index.js

// Logger middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next(); // Don't forget this!
});

Now every request logs to your terminal.
Want to use 3rd-party middleware? Try cors or morgan:

npm install cors morgan
const cors = require("cors");
const morgan = require("morgan");

app.use(cors());
app.use(morgan("dev"));

Now it’s starting to feel like a real app 🚀


Step 6: Add File Persistence with fs (optional) 💾

Let’s store notes in a JSON file:

const fs = require("fs");
const path = "./data.json";

// Save to file
function saveNotes(notes) {
  fs.writeFileSync(path, JSON.stringify(notes, null, 2));
}

// Load from file
function loadNotes() {
  if (!fs.existsSync(path)) return [];
  return JSON.parse(fs.readFileSync(path));
}

let notes = loadNotes();

// In POST route
saveNotes(notes);

Now your notes persist even after restarting the server. Welcome to the big leagues.


Step 7: Add Environment Variables 🌱

Don’t hardcode values like ports in production. Use dotenv.

npm install dotenv

Create a .env file:

PORT=5000

In index.js:

require("dotenv").config();
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`🚀 Server running at http://localhost:${PORT}`);
});

Much cleaner. Much safer.


Step 8: Final Folder Structure 📁

express-notes-api/
├── routes/
   └── notes.js
├── data.json
├── .env
├── index.js
├── package.json

You're now running a modular, clean, production-ish API. And it still uses plain ol' JavaScript. No TypeScript, no DB (yet). Just Node + Express, done right.


What’s Next? 📈

You could...

  • Add validation with Joi or Zod
  • Plug in MongoDB or PostgreSQL
  • Use TypeScript for type safety
  • Add tests with Jest or Supertest
  • Deploy to Render, Railway, or Fly.io
  • Secure your API with auth middleware
  • Add rate limiting, pagination, etc.

Each of these can be a next blog. If you want me to write any of those parts — you know what to do 🧠


Final Words

Building with Express.js is like going from driving a bicycle to a smooth electric scooter.
Still simple. But way faster, cooler, and easier to scale.

If you made it this far — take a break, sip water, and be proud. You leveled up today.

Drop a star on an open-source project. Build your own. Or just play around with routes and see what breaks.

Keep it clean. Keep it expressive.

See you in Part 3 (maybe with databases or auth 👀).