Capstone — Build & Deploy a Full-Stack Blog FINAL CAPSTONE
Express + SQLite + vanilla JS — সবশেষে live URL
1. The Brief
You will build and deploy a tiny but real full-stack blog: an Express + SQLite API serves /posts; a static public/ folder shows the list, lets you add a new post, and renders single posts. Push it to GitHub, deploy to Render's free tier, get a public URL. This is the moment all 39 prior modules were preparing you for.
2. Project Tree
abcl-blog/
├─ package.json
├─ server.js
├─ db.js
├─ data.sqlite # gitignored — created at runtime
├─ public/
│ ├─ index.html
│ ├─ app.js
│ └─ style.css
├─ .gitignore
└─ README.md
3. package.json
{
"name": "abcl-blog",
"version": "1.0.0",
"type": "module",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"dependencies": {
"express": "^4.19.2",
"better-sqlite3": "^11.0.0"
},
"engines": { "node": ">=20" }
}
4. db.js — schema + tiny data layer
import Database from "better-sqlite3";
const db = new Database("data.sqlite");
db.pragma("journal_mode = WAL");
db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL,
created INTEGER NOT NULL DEFAULT (unixepoch())
)
`);
export const listPosts = () => db.prepare("SELECT * FROM posts ORDER BY created DESC").all();
export const getPost = (id) => db.prepare("SELECT * FROM posts WHERE id = ?").get(id);
export const createPost = (title, body) =>
db.prepare("INSERT INTO posts (title, body) VALUES (?, ?)").run(title, body);
export const deletePost = (id) =>
db.prepare("DELETE FROM posts WHERE id = ?").run(id);
5. server.js — Express API + static
import express from "express";
import { listPosts, getPost, createPost, deletePost } from "./db.js";
const app = express();
app.use(express.json());
app.use(express.static("public")); // serves index.html, app.js, style.css
// API
app.get("/api/posts", (_req, res) => res.json(listPosts()));
app.get("/api/posts/:id", (req, res) => {
const p = getPost(+req.params.id);
if (!p) return res.sendStatus(404);
res.json(p);
});
app.post("/api/posts", (req, res) => {
const { title, body } = req.body || {};
if (!title?.trim() || !body?.trim())
return res.status(400).json({ error: "title and body required" });
const r = createPost(title.trim(), body.trim());
res.status(201).json({ id: r.lastInsertRowid });
});
app.delete("/api/posts/:id", (req, res) => {
deletePost(+req.params.id);
res.sendStatus(204);
});
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`http://localhost:${port}`));
6. public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ABCL Blog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header><h1>✍️ ABCL Blog</h1></header>
<main>
<form id="form">
<input id="title" placeholder="Title" required>
<textarea id="body" placeholder="Write your post…" required></textarea>
<button>Publish</button>
</form>
<div id="list" aria-live="polite">Loading…</div>
</main>
<script src="app.js" defer></script>
</body>
</html>
7. public/app.js
const $ = (id) => document.getElementById(id);
const list = $("list");
async function refresh() {
try {
const res = await fetch("/api/posts");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const posts = await res.json();
list.replaceChildren(...posts.map(toCard));
if (!posts.length) list.textContent = "No posts yet.";
} catch (e) {
list.textContent = "Failed to load: " + e.message;
}
}
function toCard(p) {
const wrap = document.createElement("article");
wrap.className = "post";
const h = document.createElement("h2");
h.textContent = p.title;
const meta = document.createElement("p");
meta.className = "meta";
meta.textContent = new Date(p.created * 1000).toLocaleString();
const body = document.createElement("p");
body.textContent = p.body;
const del = document.createElement("button");
del.textContent = "Delete";
del.addEventListener("click", async () => {
if (!confirm("Delete?")) return;
await fetch(`/api/posts/${p.id}`, { method: "DELETE" });
refresh();
});
wrap.append(h, meta, body, del);
return wrap;
}
$("form").addEventListener("submit", async (e) => {
e.preventDefault();
const title = $("title").value.trim();
const body = $("body").value.trim();
if (!title || !body) return;
const res = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, body })
});
if (res.ok) {
e.target.reset();
refresh();
} else {
const err = await res.json().catch(() => ({}));
alert(err.error || "Failed to create");
}
});
refresh();
8. public/style.css (compact)
* { box-sizing: border-box; }
body { font-family: system-ui, "Hind Siliguri", sans-serif; max-width: 720px; margin: auto; padding: 1.2rem; background: #fafafa; }
header h1 { margin: 0 0 1rem; }
form { display: grid; gap: 0.5rem; margin-bottom: 1.5rem; }
input, textarea, button { padding: 0.7rem; font-size: 1rem; border-radius: 6px; border: 1px solid #cbd5e1; }
textarea { min-height: 100px; resize: vertical; }
button { background: #39b549; color: white; border: none; cursor: pointer; }
button:hover { background: #2d8c3a; }
.post { background: white; padding: 1rem 1.2rem; margin-bottom: 0.8rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.post h2 { margin: 0 0 0.4rem; }
.post .meta { color: #64748b; font-size: 0.85rem; margin: 0 0 0.6rem; }
.post button { background: transparent; color: #dc2626; padding: 0.3rem 0.6rem; border: 1px solid #fecaca; }
9. .gitignore
node_modules/
data.sqlite
data.sqlite-wal
data.sqlite-shm
.env
.DS_Store
*.log
10. Deploy to Render (Free)
- Push the project to a public GitHub repo
- Sign up at
render.comwith your GitHub account - New → Web Service, pick the repo
- Build Command:
npm install - Start Command:
node server.js - Region: Singapore (closest to Bangladesh)
- Click Create; first build takes ~3 minutes
- You get a URL like
https://abcl-blog.onrender.com— open it - Note: Render free tier sleeps after 15 min idle (first request is slow). Upgrade or use a keep-alive ping if needed.
11. Sandbox-Safe Logic Test
The same data layer, in memory:
const store = (() => {
let rows = [];
let nextId = 1;
return {
list() { return [...rows].sort((a, b) => b.created - a.created); },
get(id) { return rows.find(r => r.id === id) || null; },
create(title, body) {
const r = { id: nextId++, title, body, created: Date.now() };
rows.push(r);
return r;
},
remove(id) { rows = rows.filter(r => r.id !== id); }
};
})();
store.create("Hello world", "My first post.");
store.create("হ্যালো বাংলা", "আজকের পোস্ট।");
console.log(store.list());
store.remove(1);
console.log("after delete:", store.list());
12. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Full-stack | Both frontend and backend — one person, one codebase. | Frontend + backend — এক ব্যক্তি, এক codebase। |
| SQLite | Embedded SQL database in a single file — perfect for small apps. | একটি ফাইলে embedded SQL database — ছোট app-এ আদর্শ। |
better-sqlite3 | Synchronous, fast SQLite driver for Node. | Node-এর জন্য sync ও দ্রুত SQLite driver। |
| Schema | The shape of your database tables and columns. | Database table ও column-এর গঠন। |
| WAL | Write-Ahead Logging — SQLite mode with better concurrent reads. | SQLite-এর mode — concurrent read-এ ভালো। |
| Static files | HTML/CSS/JS served as-is by the server. | Server-এর as-is পাঠানো HTML/CSS/JS। |
| REST endpoint | HTTP route + method that operates on a resource. | Resource-এ চালানো HTTP route। |
| Render (host) | Free-tier PaaS that hosts Node web services. | Free-tier PaaS — Node web service host করে। |
| Cold start | Free tier sleeps after idle; first request is slow. | Free tier idle-এ ঘুমিয়ে যায় — প্রথম request slow। |
| Public URL | The live address the world can visit. | সবাই visit করতে পারবে এমন live address। |
console.log("hello") দিয়ে শুরু — এখন একটি live deployed full-stack JavaScript app! Database, REST API, frontend, deployment — এই capstone-এ সব একসাথে। URL টি LinkedIn / CV / WhatsApp group-এ share করুন। বাকি শুধু practice — অনুশীলনই engineer তৈরি করে।
13. Capstone Checklist
-
Scaffold the project — create the folder, run
npm init -y, set"type": "module". -
Initialise git (
git init) and add the.gitignore. -
Install
expressandbetter-sqlite3as dependencies. -
Create
db.jswith the posts schema and CRUD helpers. -
Write
server.jswith the four endpoints. Runnpm run devand curl them. -
Build the static frontend in
public/— list, form, delete. -
Add server-side validation: 400 on missing title/body, 404 on unknown id.
-
Push to GitHub; create a Render Web Service from the repo.
-
Open the Render URL — create a post, refresh, delete it.
-
Share the URL on LinkedIn / your CV / WhatsApp groups. Then come back to extend it: comments, tags, simple auth, markdown rendering, pagination, search.
🎉 You Finished the Course
You started Module 01 with console.log("hello"). You've now shipped a deployed full-stack JavaScript app with a real database, a real REST API, a real frontend, and a public URL. That is what every job listing in 2026 means by "JavaScript developer."
Across forty modules you covered: language fundamentals, types and coercion, scope, closures, prototypes, classes, modules, the event loop, promises and async/await, fetch, the DOM, events, forms, storage, browser APIs, error handling, regex, functional patterns, modern tooling, TypeScript, performance, testing, Node.js, frameworks, and a full deployment.
What's next? Build three more projects — a chat app, a markdown editor, and a clone of something you use daily. Then pick React (Module 39's recommendation) and ship a serious app with it. Read other people's code on GitHub. Contribute one fix to one open-source project. The rest is reps.
আপনি Module 01-এ
console.log("hello") দিয়ে শুরু করেছিলেন — এখন একটি live deployed full-stack JavaScript app চালান, যেটিতে database, API, frontend এবং public URL সব আছে। এটিই ২০২৬ সালের প্রতিটি job listing-এর "JavaScript developer" requirement।
৪০টি module-এ আপনি কভার করেছেন — language fundamentals, types, scope, closure, prototype, class, module, event loop, Promise, async/await, fetch, DOM, event, form, storage, browser API, error handling, regex, functional, tooling, TypeScript, performance, testing, Node.js, framework এবং full deployment।
এখন কী করবেন? আরও তিনটি project বানান — একটি chat app, একটি markdown editor এবং প্রিয় কোনো app-এর clone। তারপর React শিখুন এবং একটি serious app ship করুন। GitHub-এ অন্যদের code পড়ুন; অন্তত একটি open-source project-এ একটি fix contribute করুন।
বাকি শুধু practice — অনুশীলনই আপনাকে professional engineer বানাবে। ABCL TECH-এর তরফ থেকে অসংখ্য শুভেচ্ছা। 🇧🇩