Capstone — Build & Deploy a Full-Stack Blog FINAL CAPSTONE

Express + SQLite + vanilla JS — সবশেষে live URL

~120 min Advanced 10-step capstone Live deploy

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.

আপনি একটি সম্পূর্ণ blog build করবেন: Express server, SQLite database, এবং vanilla JS frontend। শেষে Render-এ deploy করবেন এবং একটি live public URL পাবেন। এটিই capstone — যা ৩৯টি module-এর সব শিক্ষা একসাথে ব্যবহার করে।

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)

  1. Push the project to a public GitHub repo
  2. Sign up at render.com with your GitHub account
  3. New → Web Service, pick the repo
  4. Build Command: npm install
  5. Start Command: node server.js
  6. Region: Singapore (closest to Bangladesh)
  7. Click Create; first build takes ~3 minutes
  8. You get a URL like https://abcl-blog.onrender.com — open it
  9. 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:

store.js
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 (শব্দকোষ)

TermMeaningবাংলায়
Full-stackBoth frontend and backend — one person, one codebase.Frontend + backend — এক ব্যক্তি, এক codebase।
SQLiteEmbedded SQL database in a single file — perfect for small apps.একটি ফাইলে embedded SQL database — ছোট app-এ আদর্শ।
better-sqlite3Synchronous, fast SQLite driver for Node.Node-এর জন্য sync ও দ্রুত SQLite driver।
SchemaThe shape of your database tables and columns.Database table ও column-এর গঠন।
WALWrite-Ahead Logging — SQLite mode with better concurrent reads.SQLite-এর mode — concurrent read-এ ভালো।
Static filesHTML/CSS/JS served as-is by the server.Server-এর as-is পাঠানো HTML/CSS/JS।
REST endpointHTTP 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 startFree tier sleeps after idle; first request is slow.Free tier idle-এ ঘুমিয়ে যায় — প্রথম request slow।
Public URLThe live address the world can visit.সবাই visit করতে পারবে এমন live address।
আপনার অর্জন: Module 01-এ 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

  1. Scaffold the project — create the folder, run npm init -y, set "type": "module".
  2. Initialise git (git init) and add the .gitignore.
  3. Install express and better-sqlite3 as dependencies.
  4. Create db.js with the posts schema and CRUD helpers.
  5. Write server.js with the four endpoints. Run npm run dev and curl them.
  6. Build the static frontend in public/ — list, form, delete.
  7. Add server-side validation: 400 on missing title/body, 404 on unknown id.
  8. Push to GitHub; create a Render Web Service from the repo.
  9. Open the Render URL — create a post, refresh, delete it.
  10. 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-এর তরফ থেকে অসংখ্য শুভেচ্ছা। 🇧🇩

Course complete → Return to syllabus · share this URL with three friends · go build something.