SynfraCore
Synfracore
Start Learning
Navigation

Academies

Platform

RoadmapsLabsCertificationsInterviewPYQsAI AssistantCareer
Start Learning Free Learning Roadmaps

Express.jsProjects

Portfolio-ready projects to demonstrate your skills

📄
Last updated Sep 2026
Expert Content

Express.js — Portfolio Projects

Project 1

Level: Beginner

Time: 4-6 hours

GitHub repo name: express-inmemory-crud-api

What you build: A CRUD API for a single resource (e.g. "tasks") backed by an in-memory array — no database yet, so the focus stays entirely on routing, request/response handling, and basic structure.

javascript
const express = require('express');
const app = express();
app.use(express.json());

let tasks = [];
let nextId = 1;

app.get('/tasks', (req, res) => res.json(tasks));

app.get('/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === Number(req.params.id));
  if (!task) return res.status(404).json({ error: 'Task not found' });
  res.json(task);
});

app.post('/tasks', (req, res) => {
  const { title } = req.body;
  if (!title) return res.status(400).json({ error: 'title is required' });
  const task = { id: nextId++, title, done: false };
  tasks.push(task);
  res.status(201).json(task);
});

app.put('/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === Number(req.params.id));
  if (!task) return res.status(404).json({ error: 'Task not found' });
  Object.assign(task, req.body);
  res.json(task);
});

app.delete('/tasks/:id', (req, res) => {
  const index = tasks.findIndex(t => t.id === Number(req.params.id));
  if (index === -1) return res.status(404).json({ error: 'Task not found' });
  tasks.splice(index, 1);
  res.status(204).send();
});

app.listen(3000);

Project 2

Level: Intermediate

Time: 10-15 hours

GitHub repo name: express-rest-api-validated

What you build: A proper REST API for a multi-resource domain (e.g. a small blog: posts + comments), with express.Router()-based structure, request body validation, centralized error-handling middleware, and async-safe route handlers.

javascript
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  const status = err.status || 500;
  res.status(status).json({ error: err.message || 'Internal server error' });
}
module.exports = errorHandler;

// routes/posts.js
const express = require('express');
const router = express.Router();
const { z } = require('zod');

const postSchema = z.object({
  title: z.string().min(1),
  body: z.string().min(1),
});

function asyncHandler(fn) {
  return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
}

router.post('/', asyncHandler(async (req, res) => {
  const result = postSchema.safeParse(req.body);
  if (!result.success) {
    const err = new Error('Invalid post data');
    err.status = 400;
    throw err;
  }
  const post = await createPostInDb(result.data); // your database layer
  res.status(201).json(post);
}));

router.get('/:id', asyncHandler(async (req, res) => {
  const post = await findPostById(req.params.id);
  if (!post) {
    const err = new Error('Post not found');
    err.status = 404;
    throw err;
  }
  res.json(post);
}));

module.exports = router;

What this project specifically demonstrates: every error path returns a real, correctly-shaped JSON error with the right status code, and no unhandled rejection in an async route can crash the process — both genuinely production-relevant, not just "it works on the happy path."


Project 3

Level: Advanced

Time: 15-20 hours

GitHub repo name: express-jwt-auth-api

What you build: An API with JWT-based authentication middleware protecting a subset of routes — a natural forward-pointer to this platform's later Authentication technology, which covers the JWT/session-design depth this project only needs at an applied level.

javascript
// middleware/requireAuth.js
const jwt = require('jsonwebtoken');

function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing or malformed authorization header' });
  }
  const token = authHeader.split(' ')[1];
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    req.user = payload; // attach for downstream route handlers
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}
module.exports = requireAuth;

// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const requireAuth = require('./middleware/requireAuth');
const app = express();
app.use(express.json());

app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await verifyCredentials(email, password); // your own check
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });
  const token = jwt.sign({ sub: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1h' });
  res.json({ token });
});

app.get('/profile', requireAuth, (req, res) => {
  res.json({ userId: req.user.sub, email: req.user.email });
});

app.listen(3000);

What this project specifically demonstrates: auth as reusable middleware rather than repeated per-route checks, correct 401 responses for missing/invalid/expired tokens, and secrets (JWT_SECRET) kept out of source code via environment variables — the same pattern this platform's Deploying-to-production guidance treats as non-negotiable for any real secret.

Share:
Join our Community
Daily tips, job alerts, interview help — join engineers learning together
Up Next
💬
Express.jsInterview Q&A
Most asked interview questions with detailed answers
Also Worth Exploring
← Back to all Express.js modules
LabsInterview Q&A