0% found this document useful (0 votes)
4 views16 pages

Node.js Utility Functions and APIs

The document outlines various implementations and utilities in JavaScript, including an EventEmitter class, API call limiters (debounce and throttle), retry logic for async APIs, streaming file readers, and rate limiting. It also covers middleware for token validation and error handling in Express, file uploads using multer and Cloudinary, and versioning in REST APIs. Additionally, it includes examples of SQL queries, MongoDB aggregations, soft deletion, and transaction handling in PostgreSQL, as well as real-time features like chat rooms and typing indicators using Socket.io.

Uploaded by

mark karter
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views16 pages

Node.js Utility Functions and APIs

The document outlines various implementations and utilities in JavaScript, including an EventEmitter class, API call limiters (debounce and throttle), retry logic for async APIs, streaming file readers, and rate limiting. It also covers middleware for token validation and error handling in Express, file uploads using multer and Cloudinary, and versioning in REST APIs. Additionally, it includes examples of SQL queries, MongoDB aggregations, soft deletion, and transaction handling in PostgreSQL, as well as real-time features like chat rooms and typing indicators using Socket.io.

Uploaded by

mark karter
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

1) Implement your own EventEmitter class (on, emit,

off)
// [Link]
class EventEmitter {
constructor() {
[Link] = new Map();
}

on(event, listener) {
if (![Link](event)) [Link](event, new Set());
[Link](event).add(listener);
// return unsubscribe
return () => [Link](event, listener);
}

off(event, listener) {
const set = [Link](event);
if (!set) return;
[Link](listener);
if ([Link] === 0) [Link](event);
}

once(event, listener) {
const wrapper = (...args) => {
listener(...args);
[Link](event, wrapper);
};
[Link](event, wrapper);
}

emit(event, ...args) {
const set = [Link](event);
if (!set) return false;
// copy to avoid mutation during iteration
[Link](set).forEach(fn => {
try { fn(...args); } catch (err) { [Link]('Event handler error', err); }
});
return true;
}
}

// Usage example
const ee = new EventEmitter();
const unsub = [Link]('msg', (m) => [Link]('got', m));
[Link]('msg', 'hello'); // got hello
unsub();
[Link]('msg', 'no'); // nothing

[Link] = EventEmitter;
Notes: uses Set to avoid duplicate listeners and returns an unsubscribe function.

2) Function that limits API calls (debounce &


throttle)
// debounce and throttle utilities
function debounce(fn, wait) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => [Link](this, args), wait);
};
}

function throttle(fn, interval) {


let last = 0;
let timer = null;
return function (...args) {
const now = [Link]();
const remaining = interval - (now - last);
if (remaining <= 0) {
if (timer) { clearTimeout(timer); timer = null; }
last = now;
return [Link](this, args);
}
if (!timer) {
timer = setTimeout(() => {
last = [Link]();
timer = null;
[Link](this, args);
}, remaining);
}
};
}

// Example usage
const log = (x) => [Link]('call', x);
const d = debounce(log, 200);
const t = throttle(log, 500);

// Debounce: only last call after 200ms executes


d(1); d(2); d(3);

// Throttle: at most once per 500ms


t(1); t(2); setTimeout(()=>t(3), 600);

Notes: Use debounce for search input; throttle for scroll/resize or frequent events.
3) Implement retry(fn, retries) for async APIs
// [Link]
async function retry(fn, attempts = 3, delayMs = 200) {
let lastErr;
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
lastErr = err;
if (i < attempts - 1) {
await new Promise(r => setTimeout(r, delayMs * [Link](2, i))); // exponential backoff
}
}
}
throw lastErr;
}

// Example:
const unreliable = async () => {
if ([Link]() < 0.7) throw new Error('fail');
return 'ok';
};

retry(unreliable, 5).then([Link]).catch([Link]);

Notes: Uses exponential backoff; you can add jitter to reduce thundering herd.

4) Create a streaming file reader using [Link]


streams
// [Link]
const fs = require('fs');
const path = require('path');

function streamFile(filePath) {
return new Promise((resolve, reject) => {
const stream = [Link](filePath, { highWaterMark: 64 * 1024 }); // 64KB chunk
[Link]('data', (chunk) => {
// process chunk (e.g., parse, upload chunk, count)
[Link]('chunk size', [Link]);
});
[Link]('end', () => {
[Link]('done');
resolve();
});
[Link]('error', (err) => reject(err));
});
}

// usage
streamFile([Link](__dirname, '[Link]')).catch([Link]);

Notes: Use streams for large files to avoid memory spikes. For uploads to Cloudinary/S3 use
streaming upload APIs.

5) Detect and prevent blocking code in an API


Approach: monitor event-loop lag and offload heavy computations to worker_threads if detected.

// [Link]
const { monitorEventLoopDelay } = require('perf_hooks');
const { Worker } = require('worker_threads');

const h = monitorEventLoopDelay({ resolution: 10 });


[Link]();

setInterval(() => {
const lag = [Link] / 1_000_000; // ms
if (lag > 50) {
[Link](`Event loop lag high: ${[Link](2)}ms`);
}
}, 1000);

// sample offload heavy task:


function runHeavyTask(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./[Link]', { workerData: data });
[Link]('message', resolve);
[Link]('error', reject);
[Link]('exit', code => {
if (code !== 0) reject(new Error(`Worker stopped with ${code}`));
});
});
}

[Link]:

const { workerData, parentPort } = require('worker_threads');


// heavy computation
let sum = 0;
for (let i = 0; i < 1e8; i++) sum += i;
[Link](sum);

Notes: In production, prefer queueing heavy tasks to separate worker processes (BullMQ /
RabbitMQ).

6) Build a paginated API for users (Express +


Mongoose)
// models/[Link]
const mongoose = require('mongoose');
const UserSchema = new [Link]({
name: String, email: { type: String, index: true }, createdAt: { type: Date, default: [Link] }
});
[Link] = [Link]('User', UserSchema);

// routes/[Link]
const express = require('express');
const router = [Link]();
const User = require('../models/User');

[Link]('/', async (req, res, next) => {


try {
const page = [Link](1, parseInt([Link] || '1', 10));
const limit = [Link](100, parseInt([Link] || '10', 10));
const skip = (page - 1) * limit;
const [items, total] = await [Link]([
[Link]().sort({ createdAt: -1 }).skip(skip).limit(limit).lean(),
[Link]()
]);
[Link]({ page, limit, total, pages: [Link](total / limit), items });
} catch (err) { next(err); }
});

[Link] = router;

Notes: For high-scale use cursor paging (_id or createdAt cursor) to avoid high skip costs. Add
indexes.

7) Create an API rate limiter from scratch (no


library)
// [Link] - token bucket per IP (in-memory)
const buckets = new Map();

function rateLimiter({ tokensPerInterval = 10, intervalMs = 60_000 }) {


return (req, res, next) => {
const key = [Link] || [Link]['x-forwarded-for'] || 'unknown';
const now = [Link]();
let bucket = [Link](key);
if (!bucket) {
bucket = { tokens: tokensPerInterval, last: now };
[Link](key, bucket);
}
// refill
const elapsed = now - [Link];
const refill = [Link](elapsed / intervalMs) * tokensPerInterval;
if (refill > 0) {
[Link] = [Link](tokensPerInterval, [Link] + refill);
[Link] = now;
}
if ([Link] > 0) {
[Link] -= 1;
next();
} else {
[Link](429).json({ error: 'Too many requests' });
}
};
}

[Link] = rateLimiter;

Notes: In-memory solution doesn’t work with multiple instances — use Redis for distributed rate
limiting.

8) Express middleware: validate token, catch errors,


log response time
// middleware/[Link]
const jwt = require('jsonwebtoken');
const SECRET = [Link].JWT_SECRET || 'dev-secret';

function authMiddleware(req, res, next) {


const auth = [Link];
if (!auth) return [Link](401).json({ error: 'Missing auth' });
const token = [Link](' ')[1];
try {
[Link] = [Link](token, SECRET);
next();
} catch (err) {
[Link](401).json({ error: 'Invalid token' });
}
}

[Link] = authMiddleware;

// middleware/[Link]
function responseTimeLogger(req, res, next) {
const start = [Link]();
[Link]('finish', () => {
const end = [Link]();
const ms = Number(end - start) / 1_000_000;
[Link](`${[Link]} ${[Link]} ${[Link]} - ${[Link](2)}ms`);
});
next();
}

// middleware/[Link]
function errorHandler(err, req, res, next) {
[Link](err);
[Link]([Link] || 500).json({ error: [Link] || 'Internal Server Error' });
}

[Link] = { authMiddleware, responseTimeLogger, errorHandler };

Integration:

const { authMiddleware, responseTimeLogger, errorHandler } = require('./middleware');


[Link](responseTimeLogger);
[Link]('/api', someRouter);
[Link](errorHandler);

9) Implement file upload using multer + Cloudinary


// [Link]
const cloudinary = require('cloudinary').v2;
[Link]({
cloud_name: [Link].CLOUDINARY_CLOUD,
api_key: [Link].CLOUDINARY_KEY,
api_secret: [Link].CLOUDINARY_SECRET,
});

// upload route using multer memoryStorage and streaming to cloudinary


const express = require('express');
const multer = require('multer');
const streamifier = require('streamifier');
const router = [Link]();
const upload = multer({ storage: [Link](), limits: { fileSize: 10 * 1024 * 1024 } });
[Link]('/upload', [Link]('file'), async (req, res, next) => {
try {
if (![Link]) return [Link](400).json({ error: 'No file' });
const stream = [Link].upload_stream({ folder: 'uploads' }, (error, result) => {
if (error) return next(error);
[Link]({ url: result.secure_url, public_id: result.public_id });
});
[Link]([Link]).pipe(stream);
} catch (err) { next(err); }
});

[Link] = router;

Notes: For large files use signed direct upload from client to Cloudinary to avoid server memory
usage.

10) Implement versioning in REST API (/v1/users, /


v2/users)
// routers/v1/[Link]
const r1 = require('express').Router();
[Link]('/users', (req, res) => [Link]({ version: 'v1', users: [] }));
[Link] = r1;

// routers/v2/[Link]
const r2 = require('express').Router();
[Link]('/users', (req, res) => [Link]({ version: 'v2', users: [], extras: true }));
[Link] = r2;

// [Link]
[Link]('/api/v1', require('./routers/v1/users'));
[Link]('/api/v2', require('./routers/v2/users'));

Notes: Another approach is header-based versioning or accept-version. Keep backward


compatible where possible.

11) SQL: get top 3 users with most orders


-- PostgreSQL
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
ORDER BY order_count DESC
LIMIT 3;

Notes: Add WHERE for time-range (e.g., last month). Make sure orders.user_id is indexed.

12) MongoDB aggregation to group messages by


user and count them
// Mongo shell or Mongoose aggregate
[Link]([
{ $group: { _id: "$userId", messageCount: { $sum: 1 } } },
{ $sort: { messageCount: -1 } }
]);

// With Mongoose
[Link]([
{ $group: { _id: "$userId", messageCount: { $sum: 1 } } },
{ $sort: { messageCount: -1 } }
]).then([Link]);

Notes: For large collections, ensure appropriate indexes used for other filtering stages.

13) Implement soft deletion in PostgreSQL with


deleted_at
Schema change:

ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL;


-- soft delete
UPDATE users SET deleted_at = NOW() WHERE id = $1;
-- "undelete"
UPDATE users SET deleted_at = NULL WHERE id = $1;

Select only non-deleted:

SELECT * FROM users WHERE deleted_at IS NULL;

Suggestion: Use partial index for performance:


CREATE INDEX idx_users_active ON users (id) WHERE deleted_at IS NULL;

Notes: Soft deletes keep audit trail and allow recovery. Consider policies to permanently delete
after retention.

14) Create an index on email and explain its effect


CREATE INDEX idx_users_email ON users (email);

Effect:

Speeds up WHERE email = '...' lookups and unique searches.

Slight overhead on INSERT/UPDATE because index must be maintained.

For unique constraint, use CREATE UNIQUE INDEX or ALTER TABLE ADD CONSTRAINT
UNIQUE.

Mongoose:

const UserSchema = new [Link]({ email: { type: String, index: true } });

15) Write a transaction that transfers money


between two users (Postgres using pg)
// [Link]
const { Pool } = require('pg');
const pool = new Pool();

async function transfer(fromId, toId, amount) {


const client = await [Link]();
try {
await [Link]('BEGIN');
// Lock the rows to avoid race
const fromRes = await [Link]('SELECT balance FROM accounts WHERE user_id=$1 FOR
UPDATE', [fromId]);
const toRes = await [Link]('SELECT balance FROM accounts WHERE user_id=$1 FOR
UPDATE', [toId]);

if (![Link][0] || ![Link][0]) throw new Error('Account not found');

if ([Link][0].balance < amount) throw new Error('Insufficient funds');

await [Link]('UPDATE accounts SET balance = balance - $1 WHERE user_id=$2', [amount,


fromId]);
await [Link]('UPDATE accounts SET balance = balance + $1 WHERE user_id=$2',
[amount, toId]);

// optional: insert ledger rows


await [Link]('COMMIT');
return true;
} catch (err) {
await [Link]('ROLLBACK');
throw err;
} finally {
[Link]();
}
}

Notes: Use FOR UPDATE to serialize concurrent transfers. Use numeric/decimal types for money.

16) Implement a chat room where messages


broadcast only to room members ([Link])
// [Link]
const express = require('express');
const http = require('http');
const { Server } = require('[Link]');
const mongoose = require('mongoose');
const Message = require('./models/Message'); // mongoose model

const app = express();


const server = [Link](app);
const io = new Server(server, { cors: { origin: '*' } });

[Link]('connection', (socket) => {


[Link]('connected', [Link]);

[Link]('joinRoom', async ({ roomId, userId }) => {


[Link](roomId);
[Link] = roomId;
[Link] = userId;
[Link](roomId).emit('userJoined', { userId });
});

[Link]('sendMessage', async ({ roomId, text, userId }) => {


// persist to db
const msg = await [Link]({ roomId, userId, text, createdAt: new Date() });
// send to room
[Link](roomId).emit('newMessage', msg);
});

[Link]('disconnect', () => {
if ([Link]) [Link]([Link]).emit('userLeft', { userId: [Link] });
});
});

[Link](3000);

Mongoose model (Message):

const mongoose = require('mongoose');


const schema = new [Link]({
roomId: String, userId: String, text: String, createdAt: Date
});
[Link] = [Link]('Message', schema);

Notes: For scaling, add Redis adapter to share messages across instances.

17) Build a typing-indicator system with socket


events
// On server (continue from above)
[Link]('typing', ({ roomId, userId }) => {
[Link](roomId).emit('typing', { userId });
});
[Link]('stopTyping', ({ roomId, userId }) => {
[Link](roomId).emit('stopTyping', { userId });
});

Client-side (pseudo):

let typingTimeout;
function userTyping() {
[Link]('typing', { roomId, userId });
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
[Link]('stopTyping', { roomId, userId });
}, 1000); // stop after 1s inactivity
}

Notes: Debounce on client so too many events aren't emitted. Consider presence/last-active
channels.

18) Save chat messages to MongoDB on each event


(Already shown in #16 sendMessage — re-stated with safe write)

// ensure write is not blocking socket


[Link]('sendMessage', async (payload) => {
try {
// persist, but don't block broadcast (fire-and-forget)
[Link]({ ...payload, createdAt: new Date() }).catch([Link]);
[Link]([Link]).emit('newMessage', { ...payload, tempId: [Link] });
} catch (err) {
[Link]('error', { message: 'Failed to send message' });
}
});

Notes: For guaranteed delivery, persist first then broadcast. For low latency, send broadcast then
persist (with monitoring for failures).

19) Implement JWT authentication in Express: login,


verify, protected route
// [Link]
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const router = [Link]();
const User = require('./models/User');
const SECRET = [Link].JWT_SECRET || 'dev-secret';

[Link]('/login', async (req, res) => {


const { email, password } = [Link];
const user = await [Link]({ email });
if (!user) return [Link](401).json({ error: 'Invalid' });
const ok = await [Link](password, [Link]);
if (!ok) return [Link](401).json({ error: 'Invalid' });
const token = [Link]({ userId: user._id, email: [Link] }, SECRET, { expiresIn: '15m' });
[Link]({ token });
});

// verify middleware
function authenticate(req, res, next) {
const auth = [Link];
if (!auth) return [Link](401).end();
const token = [Link](' ')[1];
try {
[Link] = [Link](token, SECRET);
next();
} catch (err) {
[Link](401).json({ error: 'Invalid token' });
}
}

// protected route
[Link]('/me', authenticate, (req, res) => {
[Link]({ userId: [Link], email: [Link] });
});

[Link] = router;

Notes: Use http-only refresh tokens for renewing access tokens (see #20). Store password
hashes with bcrypt and salted properly.

20) Create a refresh token rotation system


(invalidate old tokens)
Approach: store refresh tokens server-side (DB/Redis) with a rotation scheme: when the client
uses a refresh token, issue a new refresh token and revoke the old one.

// [Link] (simplified)
const express = require('express');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const router = [Link]();
const pool = require('./db'); // or Mongoose model RefreshToken

const ACCESS_SECRET = [Link].ACCESS_SECRET || 'access';


const REFRESH_SECRET = [Link].REFRESH_SECRET || 'refresh';

// generate tokens
function genAccess(payload) {
return [Link](payload, ACCESS_SECRET, { expiresIn: '15m' });
}
function genRefresh(payload) {
// create opaque token (safer)
return [Link](64).toString('hex');
}

// store refresh token in DB with userId, expiresAt


// Example table: refresh_tokens(token varchar primary key, user_id int, expires_at timestamptz)

[Link]('/login', async (req, res) => {


// authenticate user (omitted)
const userId = 1;
const access = genAccess({ userId });
const refresh = genRefresh();
await [Link]('INSERT INTO refresh_tokens(token, user_id, expires_at) VALUES($1,$2, NOW()
+ INTERVAL \'30 days\')', [refresh, userId]);
[Link]({ access, refresh });
});

[Link]('/token', async (req, res) => {


const { refresh } = [Link];
// validate token exists
const r = await [Link]('SELECT user_id FROM refresh_tokens WHERE token=$1', [refresh]);
if ([Link] === 0) return [Link](401).json({ error: 'Invalid refresh' });
const userId = [Link][0].user_id;

// rotate: delete old refresh, create new


await [Link]('DELETE FROM refresh_tokens WHERE token=$1', [refresh]);
const newRefresh = genRefresh();
await [Link]('INSERT INTO refresh_tokens(token, user_id, expires_at) VALUES($1,$2, NOW()
+ INTERVAL \'30 days\')', [newRefresh, userId]);
const access = genAccess({ userId });
[Link]({ access, refresh: newRefresh });
});

[Link]('/logout', async (req, res) => {


const { refresh } = [Link];
await [Link]('DELETE FROM refresh_tokens WHERE token=$1', [refresh]);
[Link]({ success: true });
});

[Link] = router;

Notes & security:

Use opaque refresh tokens stored server-side to support revocation.

Use rotate on use approach to prevent stolen token reuse — record token family or device id
to detect reuse.
Secure cookies (HttpOnly, SameSite) are recommended for storing refresh tokens on web
clients.

Set expiration and cleanup old tokens.

Production Considerations / Summary & Next Steps

Testing: add unit & integration tests for all routes and functions.

Scaling: switch in-memory stores (rate limiter, buckets) to Redis for multi-instance
deployments.

Security: always validate inputs (Zod/Joi), set CSP, use helmet, secure cookies, and rotate
keys.

Observability: log request times, errors, and use metrics (Prometheus/Grafana).

Deployment: containerize with Docker and set environment variables via secret stores.

You might also like