Skip to main content

01a-api-rest

Certainly! When it comes to REST APIs in the context of TypeScript and Node.js, there are several subtopics and types of implementation you might be asked about. I'll outline some of these along with brief descriptions and example implementations where applicable.

Types of REST API Implementations

  1. Basic CRUD Operations

    • Implementing Create, Read, Update, and Delete (CRUD) operations using a database (e.g., MongoDB, PostgreSQL).
    • Example: Using Express with Mongoose (for MongoDB) to create endpoints for CRUD operations.
  2. File Uploads and Downloads

    • Handling file uploads (e.g., images, documents) and downloads.
    • Example: Using multer for file upload handling in Express.
  3. Authentication Endpoints

    • Implementing endpoints for user authentication.
    • Example: Using JSON Web Tokens (JWT) for user authentication and authorization.
  4. Pagination and Filtering

    • Creating APIs that support pagination and filtering to efficiently handle large datasets.
    • Example: Adding query parameters to endpoints for pagination and filtering.
  5. REST API Versioning

    • Managing different versions of the API (v1, v2, etc.).
    • Example: Versioning APIs through URL paths or headers.
  6. Rate Limiting and Throttling

    • Implementing rate limiting to prevent abuse of the API.
    • Example: Using middleware like express-rate-limit.
  7. HATEOAS (Hypermedia as the Engine of Application State)

    • Implementing HATEOAS principles for a more discoverable API.
    • Example: Adding links to related resources in API responses.
  8. API Documentation (Swagger, OpenAPI)

    • Documenting the API using tools like Swagger or OpenAPI.
    • Example: Auto-generating API documentation based on code annotations.
  9. Error Handling

    • Structured error handling in APIs.
    • Example: Creating a centralized error handling middleware.
  10. Middleware Usage

    • Implementing custom middleware for various purposes (authentication, logging, validation).
    • Example: Writing middleware functions for request validation or logging.
  11. Real-time Updates (via Long Polling/WebSockets)

    • Providing real-time updates to clients (can be an extension of REST principles).
    • Example: Using Socket.io for real-time communication.
  12. GraphQL Integration (as an Alternative/Complement to REST)

    • Integrating GraphQL as an alternative to traditional REST APIs.
    • Example: Setting up a GraphQL server alongside REST endpoints.

Example: Basic CRUD Operations in TypeScript and Node.js

Here's a basic example of implementing CRUD operations for a hypothetical "User" resource using Express, Mongoose, and TypeScript:

import express, { Request, Response } from 'express';
import mongoose from 'mongoose';
import User from './models/User'; // Assuming User is a Mongoose model

const app = express();
app.use(express.json());

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true,
});

// Create a new user
app.post('/users', async (req: Request, res: Response) => {
const user = new User(req.body);
try {
await user.save();
res.status(201).send(user);
} catch (error) {
res.status(400).send(error);
}
});

// Read all users
app.get('/users', async (req: Request, res: Response) => {
try {
const users = await User.find({});
res.send(users);
} catch (error) {
res.status(500).send(error);
}
});

// Other CRUD operations (update, delete) would follow a similar pattern

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

In this example, we define basic endpoints for creating and fetching users. Each route handler interacts with the MongoDB database using Mongoose. This is a simple illustration, but in a real-world application, you would include more comprehensive error handling, validation, and possibly authentication/authorization.

If you need further details or examples on any specific subtopic, feel free to ask!


For an interview focusing on Node.js and TypeScript, demonstrating your understanding of both fundamental and advanced concepts is key. Here are seven high-priority topics with brief explanations and code snippets to illustrate your knowledge:

1. Basic CRUD Operations

Implement CRUD operations in a Node.js application using Express and Mongoose for MongoDB interaction.

import express from 'express';
import mongoose from 'mongoose';
import { Request, Response } from 'express';

const app = express();
app.use(express.json());

const userSchema = new mongoose.Schema({
name: String,
age: Number
});
const User = mongoose.model('User', userSchema);

// Create User
app.post('/users', async (req: Request, res: Response) => {
const user = new User(req.body);
await user.save();
res.status(201).send(user);
});

// Read Users
app.get('/users', async (req: Request, res: Response) => {
const users = await User.find({});
res.status(200).send(users);
});

// Update User
app.patch('/users/:id', async (req: Request, res: Response) => {
const { id } = req.params;
const user = await User.findByIdAndUpdate(id, req.body, { new: true });
res.status(200).send(user);
});

// Delete User
app.delete('/users/:id', async (req: Request, res: Response) => {
const { id } = req.params;
await User.findByIdAndDelete(id);
res.status(204).send();
});

2. File Uploads and Downloads

Handling file uploads using multer middleware in an Express.js application.

import express from 'express';
import multer from 'multer';

const app = express();
const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('file'), (req, res) => {
res.status(200).send('File uploaded successfully.');
});

app.get('/download/:filename', (req, res) => {
const { filename } = req.params;
res.download(`uploads/${filename}`);
});

3. Middleware and Error Handling

Implementing custom middleware and error handling in Express.js.

import express, { Request, Response, NextFunction } from 'express';

const app = express();

// Custom Middleware
app.use((req: Request, res: Response, next: NextFunction) => {
console.log(`Request received: ${req.method} ${req.url}`);
next();
});

// Error Handling Middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});

4. WebSockets with Socket.io

Establishing real-time communication using Socket.io.

import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';

const app = express();
const server = createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
console.log('User connected');
socket.on('message', (msg) => {
io.emit('message', msg);
});
});

server.listen(3000);

5. REST API Design

Designing a RESTful API with proper endpoint structuring.

import express from 'express';

const app = express();
app.use(express.json());

app.get('/products', (req, res) => { /* List products */ });
app.post('/products', (req, res) => { /* Create a product */ });
app.get('/products/:id', (req, res) => { /* Retrieve a product */ });
app.put('/products/:id', (req, res) => { /* Update a product */ });
app.delete('/products/:id', (req, res) => { /* Delete a product */ });

app.listen(3000);

6. Environment Variable Management

Managing environment variables securely.

import dotenv from 'dotenv';
dotenv.config();

console.log(`Database URL: ${process.env.DATABASE_URL}`);

7. Async/Await and Error Handling

Handling asynchronous operations and errors in a modern way.

import express, { Request, Response } from 'express';
import mongoose from 'mongoose';

const app = express();

app.get('/data', async (req: Request, res: Response) => {
try {
const data = await mongoose.model('Data').find();
res.json(data);
} catch (error) {
res.status(500).send(error.message);
}
});

These examples cover a range of topics that demonstrate not only your technical skills but also your understanding of best practices and modern approaches in Node.js and TypeScript development. Feel free to adapt or expand these snippets based on your personal experience or the specifics of the job role.


For an interview focusing on Node.js and TypeScript, showcasing your ability to implement practical and essential features like authentication and efficient data handling is crucial. Here are key points with code snippets for authentication endpoints and pagination/filtering:

3. Authentication Endpoints

Implementing secure authentication using JSON Web Tokens (JWT) in a Node.js application.

import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';

const app = express();
app.use(express.json());

// User login endpoint
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = /* find user in the database */;

if (!user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).send('Invalid credentials');
}

const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});

4. Pagination and Filtering

Creating APIs with pagination and filtering capabilities to manage large datasets efficiently.

import express from 'express';

const app = express();

app.get('/items', (req, res) => {
let { page, limit, filter } = req.query;
page = parseInt(page) || 1;
limit = parseInt(limit) || 10;

// Mock database query with pagination and filtering
const items = /* fetch items from database using page, limit, and filter */
res.json(items);
});

5. Password Hashing and Salting

Securely storing user passwords using hashing and salting techniques.

import bcrypt from 'bcryptjs';

const hashPassword = async (password: string) => {
const salt = await bcrypt.genSalt(10);
return bcrypt.hash(password, salt);
};

6. JWT Verification Middleware

Creating middleware for verifying JWT and protecting routes.

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

const verifyToken = (req: Request, res: Response, next: NextFunction) => {
const token = req.header('Authorization')?.split(' ')[1];
if (!token) return res.status(401).send('Access Denied');

try {
const verified = jwt.verify(token, process.env.JWT_SECRET);
req.user = verified;
next();
} catch (err) {
res.status(400).send('Invalid Token');
}
};

7. Role-Based Access Control

Implementing role-based access control in authentication.

const checkRole = (role: string) => {
return (req: Request, res: Response, next: NextFunction) => {
if (req.user.role !== role) {
return res.status(403).send('Access Denied');
}
next();
};
};

8. Refresh Tokens

Implementing refresh tokens for maintaining user sessions.

app.post('/token', (req, res) => {
const { refreshToken } = req.body;
// Verify refresh token and issue a new JWT
});

9. Error Handling in Authentication

Gracefully handling errors in authentication processes.

app.post('/login', async (req, res) => {
try {
// Login logic here
} catch (error) {
res.status(500).send('An error occurred');
}
});

These examples cover vital aspects of authentication and data management, demonstrating your practical skills and understanding of best practices in Node.js and TypeScript development. Adapt or expand these snippets based on your experience or the specific requirements of the role you are interviewing for.


For an interview centered around Node.js and TypeScript, demonstrating your understanding of REST API versioning, rate limiting, and HATEOAS can significantly showcase your expertise. Here are key points with code snippets:

5. REST API Versioning

Managing API versions to accommodate evolving features while maintaining backward compatibility.

import express from 'express';

const app = express();

// Version 1
app.get('/api/v1/items', (req, res) => {
res.json({ message: 'Welcome to API Version 1' });
});

// Version 2
app.get('/api/v2/items', (req, res) => {
res.json({ message: 'Welcome to API Version 2', newFeature: '...' });
});

6. Rate Limiting and Throttling

Implementing rate limiting to protect the API from excessive use or abuse.

import express from 'express';
import rateLimit from 'express-rate-limit';

const app = express();

const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});

// Apply rate limiting to all requests
app.use(limiter);

7. HATEOAS (Hypermedia as the Engine of Application State)

Enhancing API discoverability and navigation by implementing HATEOAS principles.

app.get('/api/items/:id', (req, res) => {
const itemId = req.params.id;
// Fetch item details
const item = /* Fetch item from database */;

item.links = {
self: `http://${req.headers.host}/api/items/${itemId}`,
allItems: `http://${req.headers.host}/api/items`
};

res.json(item);
});

8. Header-Based API Versioning

Managing API versions using request headers.

app.use((req, res, next) => {
const apiVersion = req.headers['accept-version'] || 'v1';
req.apiVersion = apiVersion;
next();
});

app.get('/api/items', (req, res) => {
if (req.apiVersion === 'v1') {
// Handle version 1
} else {
// Handle other versions
}
});

9. Custom Rate Limiting Strategy

Creating a custom rate-limiting strategy based on user or API key.

const customLimiter = rateLimit({
keyGenerator: (req) => req.user.id || req.ip, // Rate limit based on user ID or IP
// Other configurations...
});

10. Dynamic API Versioning

Implementing dynamic versioning where the version can be specified in the request.

app.use('/api/:version/items', (req, res, next) => {
const version = req.params.version;
// Handle different versions
});

Adding navigational links in API responses for related resources.

app.get('/api/books/:id', (req, res) => {
const bookId = req.params.id;
const book = /* Fetch book details */;
book.links = {
self: `http://${req.headers.host}/api/books/${bookId}`,
author: `http://${req.headers.host}/api/authors/${book.authorId}`
};
res.json(book);
});

These examples demonstrate a blend of essential REST API practices and advanced techniques, showing your capability to build and manage scalable, user-friendly APIs. Tailor these snippets to your experience or the specific job role for the best impact in your interview.


For an interview centered on Node.js and TypeScript, demonstrating your ability to handle errors effectively and use middleware efficiently is crucial. Here are key points with code snippets:

9. Error Handling

Implementing structured and centralized error handling in a Node.js API.

import express, { Request, Response, NextFunction, ErrorRequestHandler } from 'express';

const app = express();

// Error handling middleware
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
};

app.use(errorHandler);

// Example usage
app.get('/error', (req, res) => {
throw new Error('This is an error!');
});

10. Middleware Usage

Writing custom middleware for various purposes such as logging, authentication, or validation.

// Logging middleware
const loggingMiddleware = (req: Request, res: Response, next: NextFunction) => {
console.log(`Incoming request: ${req.method} - ${req.path}`);
next();
};

app.use(loggingMiddleware);

11. Custom Error Classes

Creating custom error classes for more structured error handling.

class AppError extends Error {
statusCode: number;

constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
}
}

// Using custom error
app.get('/custom-error', (req, res, next) => {
const err = new AppError('Not Found', 404);
next(err);
});

12. Validation Middleware

Implementing validation middleware to check request data.

const validationMiddleware = (req: Request, res: Response, next: NextFunction) => {
if (!req.body.name) {
return res.status(400).send('Name is required');
}
next();
};

app.post('/validate', validationMiddleware, (req, res) => {
res.send('Validated!');
});

13. Error Propagation

Properly propagating errors in async functions.

app.get('/async-error', async (req, res, next) => {
try {
// Async operation
} catch (error) {
next(error);
}
});

14. Authentication Middleware

Creating middleware for user authentication.

const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const user = /* Check user authentication */;
if (!user) {
return res.status(401).send('Unauthorized');
}
req.user = user;
next();
};

app.use('/secure', authMiddleware);

15. Centralized Error Formatting

Formatting error responses in a consistent structure.

const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
res.status(err.statusCode || 500).json({
error: {
message: err.message,
code: err.statusCode
}
});
};

16. Conditional Middleware Execution

Applying middleware conditionally based on the route or other criteria.

const conditionalMiddleware = (condition: boolean) => {
return (req: Request, res: Response, next: NextFunction) => {
if (condition) {
console.log('Condition met, middleware executed');
}
next();
};
};

app.use(conditionalMiddleware(true));

These examples highlight important aspects of error handling and middleware usage in Node.js and TypeScript applications, showcasing your practical skills and understanding of best practices. Adapt or expand these snippets based on your personal experience or the specific job role you are interviewing for.


Demonstrating your expertise in implementing real-time updates via technologies like WebSockets and long polling is crucial for Node.js and TypeScript-focused roles. Here are key points with code snippets:

11. Real-time Updates (via Long Polling/WebSockets)

Implementing real-time communication and updates in a Node.js application.

Using Socket.io for WebSockets

import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';

const app = express();
const server = createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
console.log('A user connected');

socket.on('sendMessage', (message) => {
io.emit('receiveMessage', message);
});

socket.on('disconnect', () => {
console.log('User disconnected');
});
});

server.listen(3000, () => {
console.log('Server listening on port 3000');
});

Long Polling Technique

const messages: string[] = [];
const messageWaiters: Function[] = [];

app.get('/messages', (req, res) => {
if (messages.length > 0) {
res.json(messages);
messages.length = 0;
} else {
messageWaiters.push(res);
}
});

app.post('/messages', (req, res) => {
messages.push(req.body.message);
messageWaiters.forEach((waiter) => waiter.json(messages));
messageWaiters.length = 0;
res.status(200).send();
});

Emitting Events to Clients

io.on('connection', (socket) => {
socket.emit('update', { data: 'Initial data' });

socket.on('requestUpdate', () => {
socket.emit('update', { data: 'Updated data' });
});
});

Broadcast Messages to All Clients

io.on('connection', (socket) => {
socket.on('newMessage', (msg) => {
io.emit('broadcastMessage', msg);
});
});

Real-time Notifications

io.on('connection', (socket) => {
socket.on('subscribeToNotifications', (userId) => {
// Logic to subscribe user to notifications
});
});

Efficiently Handling Disconnections

io.on('connection', (socket) => {
socket.on('disconnect', () => {
console.log(`User ${socket.id} disconnected`);
// Handle disconnection logic
});
});

Real-time Chat Application Example

io.on('connection', (socket) => {
socket.on('joinChat', (chatId) => {
socket.join(chatId);
});

socket.on('sendChatMessage', (chatId, message) => {
io.to(chatId).emit('newChatMessage', message);
});
});

Handling Connection Errors

io.on('connection', (socket) => {
socket.on('error', (err) => {
console.error('Socket encountered error:', err);
});
});

These examples cover various aspects of implementing real-time communication in a Node.js application using WebSockets and long polling, demonstrating your ability to build dynamic and interactive applications. Adapt these snippets to align with your personal experiences or the specifics of the job role you are interviewing for.


Integrating GraphQL in a Node.js and TypeScript application can showcase your adaptability and understanding of modern API development practices. Here are key points with code snippets:

12. GraphQL Integration (as an Alternative/Complement to REST)

Implementing GraphQL as an efficient and flexible alternative or complement to REST APIs.

Setting Up a GraphQL Server

import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

// GraphQL schema
const schema = buildSchema(`
type Query {
message: String
}
`);

// Root resolver
const root = {
message: () => 'Hello World!'
};

const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));

app.listen(3000, () => console.log('Server running on port 3000'));

Defining a GraphQL Schema

const schema = buildSchema(`
type Query {
user(id: String!): User
}

type User {
id: String
name: String
email: String
}
`);

GraphQL Resolvers

const root = {
user: (args: { id: string }) => {
// Fetch user from database
return { id: args.id, name: 'John Doe', email: 'john@example.com' };
}
};

GraphQL Mutations

const schema = buildSchema(`
type Mutation {
createUser(name: String!, email: String!): User
}

type User {
id: String
name: String
email: String
}
`);

const root = {
createUser: ({ name, email }) => {
// Logic to create a user
return { id: '1', name, email };
}
};

Integrating GraphQL with Existing REST Endpoints

app.use('/api/users', userRouter); // REST endpoint
app.use('/graphql', graphqlHTTP({ schema, rootValue: root, graphiql: true })); // GraphQL endpoint

Advanced GraphQL Queries

const schema = buildSchema(`
type Query {
users(age: Int): [User]
}

type User {
id: String
name: String
age: Int
}
`);

const root = {
users: ({ age }) => {
// Fetch users based on age
}
};

Error Handling in GraphQL

const root = {
user: (args: { id: string }) => {
try {
// Fetch user
} catch (error) {
throw new Error('Failed to fetch user');
}
}
};

These examples demonstrate how to set up and utilize a GraphQL server in a Node.js application, highlighting the flexibility and efficiency of GraphQL compared to traditional REST APIs. Tailor these examples to your experiences or the specific requirements of the job role you're interviewing for.

Certainly! Expanding on the integration of GraphQL in a Node.js and TypeScript environment, here are additional key points and code snippets:

13. Subscription for Real-time Updates

Implementing GraphQL subscriptions for real-time updates, a powerful feature for dynamic applications.

import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();
const MESSAGE_ADDED = 'MESSAGE_ADDED';

const schema = buildSchema(`
type Subscription {
messageAdded: Message
}

type Message {
id: String
content: String
}
`);

const root = {
subscription: {
messageAdded: {
subscribe: () => pubsub.asyncIterator([MESSAGE_ADDED])
}
}
};

// Triggering subscription
pubsub.publish(MESSAGE_ADDED, { messageAdded: { id: '1', content: 'Hello World' } });

14. Batch and Cache GraphQL Requests

Utilizing DataLoader for batching and caching to improve performance by reducing the number of database calls.

import DataLoader from 'dataloader';

const userLoader = new DataLoader(keys => batchGetUsers(keys));

const schema = buildSchema(`
type Query {
user(id: String!): User
}
`);

const root = {
user: ({ id }) => userLoader.load(id)
};

// Where 'batchGetUsers' is a function that fetches users in a batch

15. GraphQL Error Handling

Providing detailed and user-friendly error messages in the GraphQL responses.

const root = {
user: (args: { id: string }) => {
try {
// Fetch user logic
} catch (error) {
throw new Error('Error fetching user: ' + error.message);
}
}
};

16. Custom GraphQL Scalars

Defining custom scalar types in GraphQL for specific data formats like Date or URL.

import { GraphQLScalarType } from 'graphql';

const schema = buildSchema(`
scalar Date

type Query {
currentDate: Date
}
`);

const dateScalar = new GraphQLScalarType({
name: 'Date',
serialize(value) {
return value.toISOString(); // Convert Date to string
},
parseValue(value) {
return new Date(value); // Convert string to Date
}
});

const root = {
Date: dateScalar,
currentDate: () => new Date()
};

17. Nested Queries and Relationships

Handling nested queries and relationships between different types in GraphQL.

const schema = buildSchema(`
type Query {
user(id: String!): User
}

type User {
id: String
name: String
posts: [Post]
}

type Post {
id: String
title: String
author: User
}
`);

const root = {
user: ({ id }) => {
// Fetch user and their posts
},
post: ({ id }) => {
// Fetch post and its author
}
};

18. Directives in GraphQL

Using directives in GraphQL to modify the execution of queries and mutations, such as for permissions or logging.

const schema = buildSchema(`
directive @log on FIELD_DEFINITION

type Query {
user(id: String!): User @log
}
`);

// The @log directive can be used to add logging logic

These additional points cover advanced aspects of GraphQL, demonstrating your ability to implement efficient, scalable, and flexible API solutions. Customizing these examples based on your personal experiences and the job's specific requirements will further enhance your interview responses.