JavaScript + Backend Bootcamp
Day 10 : Node.js Core Modules + HTTP Server
Today you’ll learn how Node.js works with:
- Files and directories
- Paths
- Operating-system information
- Events
- Buffers
- Streams
- HTTP requests and responses
- Routes
- Status codes
- JSON APIs
You’ll also build:
- A TypeScript File Manager
- A Basic TypeScript API Server
Node’s HTTP module is intentionally low-level. It gives direct access to request and response objects, which makes it a useful foundation before learning Express.js. (Node.js)
Prerequisite
Continue using the project created on Day 9.
day-10-node-typescript/
│
├── src/
│ └── index.ts
│
├── dist/
├── .env
├── package.json
└── tsconfig.jsonDevelopment command:
npm run devType-check:
npm run typecheckProduction build:
npm run build
npm start1. Node.js Core Modules
Node.js includes built-in modules that do not require installation from npm.
Today we’ll use:
node:fs
node:path
node:os
node:events
node:http
node:stream
node:bufferUse the node: prefix to make it clear that a module is built into Node.js.
Example:
import path from "node:path";2. File System Module
The node:fs module lets applications work with files and directories.
It supports:
- Callback-based APIs
- Synchronous APIs
- Promise-based APIs
- File streams
For most backend code, prefer the Promise API because synchronous file operations block further JavaScript execution while they run. (Node.js)
Import it:
import { readFile, writeFile } from "node:fs/promises";3. Write a File
Create:
src/files/write-file.tsimport { writeFile } from "node:fs/promises";async function createFile(): Promise<void> {
try {
await writeFile(
"message.txt",
"Hello from Node.js and TypeScript",
"utf8"
); console.log("File created successfully");
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
}
}
}await createFile();
Run:
npx tsx src/files/write-file.tsThis creates:
message.txt4. Read a File
import { readFile } from "node:fs/promises";async function readMessage(): Promise<void> {
try {
const content = await readFile(
"message.txt",
"utf8"
); console.log(content);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(
`Unable to read file: ${error.message}`
);
}
}
}await readMessage();
Without "utf8", readFile() returns a Buffer.
5. Append to a File
import { appendFile } from "node:fs/promises";async function addLog(): Promise<void> {
const log = `Application started at ${new Date().toISOString()}\n`; await appendFile(
"application.log",
log,
"utf8"
); console.log("Log added");
}await addLog();
This is useful for understanding basic logging, although production applications normally use dedicated logging libraries.
6. Delete a File
import { unlink } from "node:fs/promises";async function removeFile(): Promise<void> {
try {
await unlink("message.txt"); console.log("File deleted");
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
}
}
}await removeFile();
7. Check Whether a File Exists
import { access } from "node:fs/promises";async function fileExists(
filePath: string
): Promise<boolean> {
try {
await access(filePath);
return true;
} catch {
return false;
}
}console.log(await fileExists("message.txt"));
8. Create a Directory
import { mkdir } from "node:fs/promises";async function createDirectory(): Promise<void> {
await mkdir("data/users", {
recursive: true
}); console.log("Directory created");
}await createDirectory();
The recursive option creates missing parent directories.
9. Read Directory Contents
import { readdir } from "node:fs/promises";async function listFiles(): Promise<void> {
const files = await readdir("data"); console.log(files);
}await listFiles();
To get information about whether each entry is a file or directory:
import { readdir } from "node:fs/promises";const entries = await readdir("data", {
withFileTypes: true
});for (const entry of entries) {
console.log({
name: entry.name,
isFile: entry.isFile(),
isDirectory: entry.isDirectory()
});
}
10. Rename or Move a File
import { rename } from "node:fs/promises";await rename(
"old-name.txt",
"new-name.txt"
);console.log("File renamed");
It can also move files:
await rename(
"report.txt",
"data/report.txt"
);11. File Information with stat()
import { stat } from "node:fs/promises";const information = await stat("application.log");console.log({
size: information.size,
created: information.birthtime,
modified: information.mtime,
isFile: information.isFile()
});
12. Path Module
Paths differ between operating systems.
Avoid manually joining paths like this:
const filePath = "data/" + "users/" + "user.json";Use node:path:
import path from "node:path";const filePath = path.join(
"data",
"users",
"user.json"
);console.log(filePath);
13. Common Path Methods
path.join()
import path from "node:path";const filePath = path.join(
process.cwd(),
"data",
"users.json"
);console.log(filePath);
path.resolve()
Creates an absolute path:
const fullPath = path.resolve(
"data",
"users.json"
);console.log(fullPath);path.basename()
const name = path.basename(
"/projects/api/users.json"
);console.log(name);Output:
users.jsonpath.dirname()
const directory = path.dirname(
"/projects/api/users.json"
);console.log(directory);path.extname()
const extension = path.extname(
"users.json"
);console.log(extension);Output:
.jsonpath.parse()
const information = path.parse(
"/projects/api/users.json"
);console.log(information);Possible output:
{
root: "/",
dir: "/projects/api",
base: "users.json",
ext: ".json",
name: "users"
}14. Working with the Current Module Directory
In ES modules, __dirname is not available automatically.
Use:
import path from "node:path";
import { fileURLToPath } from "node:url";const currentFile = fileURLToPath(
import.meta.url
);const currentDirectory = path.dirname(
currentFile
);console.log(currentFile);
console.log(currentDirectory);
15. Operating System Module
Import:
import os from "node:os";Get system information:
console.log({
platform: os.platform(),
architecture: os.arch(),
hostname: os.hostname(),
homeDirectory: os.homedir(),
temporaryDirectory: os.tmpdir(),
totalMemory: os.totalmem(),
freeMemory: os.freemem(),
cpuCount: os.cpus().length
});Convert bytes to gigabytes:
function bytesToGigabytes(
bytes: number
): string {
return (
bytes / 1024 / 1024 / 1024
).toFixed(2);
}console.log(
`${bytesToGigabytes(os.totalmem())} GB`
);16. Events Module
Node.js uses events heavily.
Import:
import { EventEmitter } from "node:events";Create an event emitter:
const emitter = new EventEmitter();emitter.on("message", (message: string) => {
console.log(message);
});emitter.emit(
"message",
"Hello from EventEmitter"
);
17. Typed Event Payload
import { EventEmitter } from "node:events";interface UserCreatedEvent {
id: number;
name: string;
email: string;
}const userEvents = new EventEmitter();userEvents.on(
"userCreated",
(user: UserCreatedEvent) => {
console.log(
`User created: ${user.name}`
);
}
);userEvents.emit("userCreated", {
id: 1,
name: "Ravi",
email: "ravi@example.com"
});
Note that the base EventEmitter API does not automatically enforce relationships between event names and their payload types. Later, a typed wrapper can provide stricter event safety.
18. once()
A listener registered with once() runs only one time.
const emitter = new EventEmitter();emitter.once("connected", () => {
console.log("Connected for the first time");
});emitter.emit("connected");
emitter.emit("connected");
Output:
Connected for the first time19. Remove an Event Listener
const emitter = new EventEmitter();function handleMessage(message: string): void {
console.log(message);
}emitter.on("message", handleMessage);emitter.emit("message", "First message");emitter.off("message", handleMessage);emitter.emit("message", "Second message");
Only the first message is printed.
20. Buffers
A Buffer represents binary data.
const buffer = Buffer.from(
"Hello Node.js",
"utf8"
);console.log(buffer);
console.log(buffer.toString("utf8"));Buffers are commonly encountered when working with:
- Files
- Network data
- Images
- Uploads
- Streams
21. Allocate a Buffer
const buffer = Buffer.alloc(4);buffer[0] = 72;
buffer[1] = 105;console.log(buffer.toString());
Output:
Hi22. Streams
Streams process data in smaller pieces rather than loading everything into memory at once.
Node.js streams work with strings and binary values such as Buffer and typed-array objects. HTTP requests, HTTP responses and file streams all use streaming concepts. (Node.js)
The main stream categories are:
- Readable
- Writable
- Duplex
- Transform
23. Readable File Stream
import { createReadStream } from "node:fs";const stream = createReadStream(
"large-file.txt",
{
encoding: "utf8",
highWaterMark: 64
}
);stream.on("data", (chunk) => {
console.log("Chunk received:");
console.log(chunk);
});stream.on("end", () => {
console.log("Finished reading");
});stream.on("error", (error) => {
console.error(error.message);
});
24. Writable File Stream
import { createWriteStream } from "node:fs";const stream = createWriteStream(
"output.txt",
{
encoding: "utf8"
}
);stream.write("First line\n");
stream.write("Second line\n");
stream.end("Final line\n");stream.on("finish", () => {
console.log("Writing completed");
});
25. Pipe Streams
import {
createReadStream,
createWriteStream
} from "node:fs";const source = createReadStream(
"large-file.txt"
);const destination = createWriteStream(
"large-file-copy.txt"
);source.pipe(destination);
A safer modern approach is pipeline().
26. Stream Pipeline
import {
createReadStream,
createWriteStream
} from "node:fs";import { pipeline } from "node:stream/promises";async function copyFile(): Promise<void> {
await pipeline(
createReadStream("large-file.txt"),
createWriteStream("copy.txt")
); console.log("File copied");
}try {
await copyFile();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
}
}
The Promise-based stream utilities make it easier to handle completion and errors using async/await. (Node.js)
27. HTTP Module
Import:
import { createServer } from "node:http";Node’s server callback receives:
IncomingMessageServerResponse
The request object contains information such as the URL, method, headers and incoming body stream. The response object is used to set status, headers and response content. (Node.js)
28. Create Your First HTTP Server
Create:
src/server.tsimport { createServer } from "node:http";const port = 3000;const server = createServer(
(request, response) => {
response.statusCode = 200; response.setHeader(
"Content-Type",
"text/plain; charset=utf-8"
); response.end("Hello from Node.js");
}
);server.listen(port, () => {
console.log(
`Server running at http://localhost:${port}`
);
});
Run:
npx tsx src/server.tsOpen:
http://localhost:300029. Request Information
const server = createServer(
(request, response) => {
console.log({
method: request.method,
url: request.url,
headers: request.headers
}); response.end("Request received");
}
);30. Send a JSON Response
import { createServer } from "node:http";const server = createServer(
(_request, response) => {
const data = {
success: true,
message: "API is working"
}; response.writeHead(200, {
"Content-Type":
"application/json; charset=utf-8"
}); response.end(JSON.stringify(data));
}
);server.listen(3000);
31. HTTP Methods
Common methods:
GET Read data
POST Create data
PUT Replace data
PATCH Partially update data
DELETE Delete dataRead the method:
const method = request.method ?? "GET";32. HTTP Status Codes
Common API status codes:
200 OK
201 Created
204 No Content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
500 Internal Server ErrorExample:
response.statusCode = 404;
response.end("Route not found");33. Basic Routing
import { createServer } from "node:http";const server = createServer(
(request, response) => {
const method = request.method;
const url = request.url; if (method === "GET" && url === "/") {
response.writeHead(200, {
"Content-Type":
"application/json; charset=utf-8"
}); response.end(
JSON.stringify({
message: "Welcome to the API"
})
); return;
} if (
method === "GET" &&
url === "/health"
) {
response.writeHead(200, {
"Content-Type":
"application/json; charset=utf-8"
}); response.end(
JSON.stringify({
status: "healthy",
uptime: process.uptime()
})
); return;
} response.writeHead(404, {
"Content-Type":
"application/json; charset=utf-8"
}); response.end(
JSON.stringify({
message: "Route not found"
})
);
}
);server.listen(3000, () => {
console.log(
"Server running at http://localhost:3000"
);
});
34. Parse a URL
The request URL may contain:
/users?page=2&limit=10Use the URL class:
const url = new URL(
request.url ?? "/",
`http://${request.headers.host ?? "localhost"}`
);console.log(url.pathname);
console.log(url.searchParams.get("page"));
console.log(url.searchParams.get("limit"));35. Query Parameters
Example request:
GET /users?role=adminconst requestUrl = new URL(
request.url ?? "/",
"http://localhost"
);const role = requestUrl.searchParams.get(
"role"
);console.log(role);
36. Route Parameters
Node’s built-in HTTP module does not provide automatic route-parameter parsing.
Get Bhaskar Chowdary’s stories in your inbox
Join Medium for free to get updates from this writer.
For:
GET /users/10You can parse it manually:
const parts = requestUrl.pathname
.split("/")
.filter(Boolean);if (
request.method === "GET" &&
parts[0] === "users" &&
parts[1]
) {
const userId = Number(parts[1]); if (!Number.isInteger(userId)) {
// Invalid ID
}
}
Express will make this easier on Day 11.
37. Reading a Request Body
An HTTP request body arrives as a stream.
import type {
IncomingMessage
} from "node:http";async function readRequestBody(
request: IncomingMessage
): Promise<string> {
const chunks: Buffer[] = []; for await (const chunk of request) {
chunks.push(
Buffer.isBuffer(chunk)
? chunk
: Buffer.from(chunk)
);
} return Buffer.concat(chunks).toString(
"utf8"
);
}
38. Parse JSON Request Body
async function readJsonBody<T>(
request: IncomingMessage
): Promise<T> {
const body = await readRequestBody(
request
);if (!body) {
throw new Error(
"Request body is required"
);
} return JSON.parse(body) as T;
}
Important:
JSON.parse(body) as Tdoes not perform runtime validation.
It only tells TypeScript to treat the result as T.
Later we’ll use Zod for actual validation.
39. Request Body Size Limit
Never accept unlimited request bodies.
async function readRequestBody(
request: IncomingMessage,
maximumBytes = 1_000_000
): Promise<string> {
const chunks: Buffer[] = [];
let totalBytes = 0;for await (const chunk of request) {
const buffer = Buffer.isBuffer(chunk)
? chunk
: Buffer.from(chunk); totalBytes += buffer.length; if (totalBytes > maximumBytes) {
throw new Error(
"Request body is too large"
);
} chunks.push(buffer);
} return Buffer.concat(chunks).toString(
"utf8"
);
}
40. Response Helper
Create:
src/http/send-json.tsimport type {
ServerResponse
} from "node:http";export function sendJson(
response: ServerResponse,
statusCode: number,
data: unknown
): void {
response.writeHead(statusCode, {
"Content-Type":
"application/json; charset=utf-8"
}); response.end(JSON.stringify(data));
}
Use it:
sendJson(response, 200, {
success: true
});41. Basic In-Memory Users API
Create:
src/server.tsimport {
createServer
} from "node:http";import type {
IncomingMessage,
ServerResponse
} from "node:http";interface User {
id: number;
name: string;
email: string;
}interface CreateUserInput {
name: string;
email: string;
}const users: User[] = [
{
id: 1,
name: "Ravi",
email: "ravi@example.com"
}
];let nextUserId = 2;function sendJson(
response: ServerResponse,
statusCode: number,
data: unknown
): void {
response.writeHead(statusCode, {
"Content-Type":
"application/json; charset=utf-8"
}); response.end(JSON.stringify(data));
}async function readBody(
request: IncomingMessage
): Promise<string> {
const chunks: Buffer[] = []; for await (const chunk of request) {
chunks.push(
Buffer.isBuffer(chunk)
? chunk
: Buffer.from(chunk)
);
} return Buffer.concat(chunks).toString(
"utf8"
);
}const server = createServer(
async (request, response) => {
try {
const method = request.method ?? "GET"; const requestUrl = new URL(
request.url ?? "/",
"http://localhost"
); const pathname = requestUrl.pathname; if (
method === "GET" &&
pathname === "/"
) {
sendJson(response, 200, {
message: "Users API"
}); return;
} if (
method === "GET" &&
pathname === "/users"
) {
sendJson(response, 200, {
success: true,
data: users
}); return;
} if (
method === "POST" &&
pathname === "/users"
) {
const body = await readBody(request); const input = JSON.parse(
body
) as CreateUserInput; if (
typeof input.name !== "string" ||
typeof input.email !== "string"
) {
sendJson(response, 400, {
message:
"Name and email are required"
}); return;
} const user: User = {
id: nextUserId++,
name: input.name,
email: input.email
}; users.push(user); sendJson(response, 201, {
success: true,
data: user
}); return;
} sendJson(response, 404, {
message: "Route not found"
});
} catch (error: unknown) {
const message =
error instanceof SyntaxError
? "Invalid JSON body"
: "Internal server error"; sendJson(
response,
error instanceof SyntaxError
? 400
: 500,
{ message }
);
}
}
);server.listen(3000, () => {
console.log(
"Server running at http://localhost:3000"
);
});
42. Test the API
Start the server:
npx tsx watch src/server.tsGet all users
curl http://localhost:3000/usersCreate a user
curl -X POST \
http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Arun","email":"arun@example.com"}'You can also use:
- Postman
- Thunder Client
- Insomnia
43. Graceful Shutdown
A server should handle termination signals.
function shutdown(signal: string): void {
console.log(
`${signal} received. Shutting down...`
);server.close((error) => {
if (error) {
console.error(error);
process.exit(1);
} console.log("Server closed");
process.exit(0);
});
}process.on("SIGINT", () => {
shutdown("SIGINT");
});process.on("SIGTERM", () => {
shutdown("SIGTERM");
});
44. Server Error Handling
server.on("error", (error) => {
console.error(
`Server error: ${error.message}`
); process.exit(1);
});Mini Project 1 — TypeScript File Manager
Create:
src/file-manager.tsimport {
access,
appendFile,
mkdir,
readFile,
unlink,
writeFile
} from "node:fs/promises";import path from "node:path";class FileManager {
constructor(
private readonly baseDirectory: string
) {} private resolvePath(
fileName: string
): string {
const safeName = path.basename(fileName); return path.join(
this.baseDirectory,
safeName
);
} async initialize(): Promise<void> {
await mkdir(this.baseDirectory, {
recursive: true
});
} async exists(
fileName: string
): Promise<boolean> {
try {
await access(
this.resolvePath(fileName)
); return true;
} catch {
return false;
}
} async create(
fileName: string,
content: string
): Promise<void> {
await writeFile(
this.resolvePath(fileName),
content,
{
encoding: "utf8",
flag: "wx"
}
);
} async read(
fileName: string
): Promise<string> {
return readFile(
this.resolvePath(fileName),
"utf8"
);
} async append(
fileName: string,
content: string
): Promise<void> {
await appendFile(
this.resolvePath(fileName),
content,
"utf8"
);
} async delete(
fileName: string
): Promise<void> {
await unlink(
this.resolvePath(fileName)
);
}
}
Use it:
const manager = new FileManager("storage");try {
await manager.initialize(); if (!(await manager.exists("notes.txt"))) {
await manager.create(
"notes.txt",
"Day 10 Node.js Notes\n"
);
} await manager.append(
"notes.txt",
"Learning the file system module\n"
); const content = await manager.read(
"notes.txt"
); console.log(content);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
}
}
Mini Project 2 — Basic TypeScript API Server
Build an API with:
GET /health
GET /users
GET /users/:id
POST /users
DELETE /users/:idRequired concepts:
node:http- Typed models
- Route parsing
- Query parameters
- JSON request bodies
- Status codes
- JSON responses
- Central error handling
- Graceful shutdown
Common Beginner Mistakes
Using synchronous file operations in request handlers
Avoid:
readFileSync("users.json");inside every HTTP request.
Synchronous file operations block the event loop until they finish. Prefer Promise-based or stream-based APIs in server code. (Node.js)
Forgetting to end the response
Wrong:
response.write("Hello");Correct:
response.end("Hello");Sending multiple responses
Wrong:
if (request.url === "/") {
response.end("Home");
}response.end("Not found");Correct:
if (request.url === "/") {
response.end("Home");
return;
}response.end("Not found");Trusting parsed JSON
This is unsafe:
const user = JSON.parse(body) as User;The request may not contain a valid user.
Check the value manually now. Later, use Zod.
Ignoring request-body limits
A client could send a very large payload.
Always define a size limit.
Building paths using user input directly
Avoid:
path.join("storage", userFileName);without checking the filename.
A value such as:
../../secret.txtcould escape the expected directory.
At minimum, use:
path.basename(userFileName);More complete protection will be discussed during the security phase.
Returning incorrect status codes
Use:
200 for successful reads
201 for successful creation
400 for invalid input
404 when a resource is missing
500 for unexpected server errorsImportant Concepts Learned Today
✅ node:fs
✅ Promise-based file operations
✅ Files and directories
✅ node:path
✅ node:os
✅ EventEmitter
✅ Buffers
✅ Readable and writable streams
✅ Stream pipelines
✅ node:http
✅ HTTP request and response objects
✅ HTTP methods
✅ Routes
✅ Query parameters
✅ Route parameters
✅ Status codes
✅ JSON request bodies
✅ JSON responses
✅ Body-size limits
✅ Error handling
✅ Graceful shutdown
✅ Typed API models
Homework
Create:
- File creation program
- File reading and updating program
- Directory listing program
- System-information program using
node:os - Custom event system using
EventEmitter - Large-file copy using streams
- Health-check HTTP route
- Users API with GET and POST
- Request-body size limit
- Graceful server shutdown
Practice Challenge
Build a typed tasks API:
GET /tasks
GET /tasks/:id
POST /tasks
PATCH /tasks/:id
DELETE /tasks/:idTask structure:
interface Task {
id: number;
title: string;
completed: boolean;
}Requirements:
- Use only Node.js core modules
- Store tasks in memory
- Return JSON responses
- Validate incoming data
- Use correct status codes
- Handle invalid JSON
- Return
404for missing tasks
Day 11
Express.js Basics with TypeScript
We’ll learn:
- Why Express is useful
- Express + TypeScript setup
- Application and router objects
- Request and response types
- Route handlers
- Middleware
- Route parameters
- Query parameters
- JSON body parsing
- Modular routes
- Typed controllers
- Error handling introduction
You’ll rebuild today’s Node.js HTTP server using Express with much less code.






