@cse-350/shared-library
v1.0.11
Published
Shared library for SkillTrade project
Readme
@cse-350/shared-library
A comprehensive TypeScript shared library for the SkillTrade microservices ecosystem, providing common utilities, event-driven communication, error handling, and middleware functionality.
š Table of Contents
- Overview
- Installation
- Architecture
- API Reference
- Usage Examples
- Event-Driven Communication
- Best Practices
- Contributing
- Version History
Overview
The @cse-350/shared-library is a foundational package that enables consistent communication and functionality across all SkillTrade microservices. It provides:
- Event-driven architecture using NATS Streaming
- Standardized error handling with custom error classes
- Common middleware for authentication, validation, and error processing
- Type definitions for consistent data structures
- Publisher/Subscriber patterns for service communication
This library ensures consistency, reduces code duplication, and maintains type safety across the entire microservices ecosystem.
Installation
npm install @cse-350/shared-libraryPeer Dependencies
npm install express jsonwebtoken cookie-session express-validator node-nats-streamingArchitecture
The shared library is organized into four main modules:
src/
āāā events/ # Event-driven communication system
ā āāā subjects.ts # Event subject definitions
ā āāā base-listener.ts
ā āāā base-publisher.ts
ā āāā *-event.ts # Specific event interfaces
ā āāā types/ # Common types
āāā errors/ # Custom error classes
āāā middlewares/ # Express middleware functions
āāā index.ts # Main export fileAPI Reference
Events System
Subjects
All event subjects are defined in a centralized enum:
export enum Subjects {
PaymentCreated = "payment:created",
ConnectionRequested = "connection:requested",
ConnectionRejected = "connection:rejected",
ConnectionAccepted = "connection:accepted",
ConnectionCancelled = "connection:cancelled",
PostDeleted = "post:deleted",
ReviewCreated = "review:created",
}Base Publisher
Abstract class for publishing events to NATS Streaming:
export abstract class Publisher<T extends Event> {
abstract subject: T['subject'];
protected client: Stan;
constructor(client: Stan);
publish(data: T['data']): Promise<void>;
}Base Listener
Abstract class for listening to events from NATS Streaming:
export abstract class Listener<T extends Event> {
abstract subject: T['subject'];
abstract queueGroupName: string;
abstract onMessage(data: T['data'], msg: Message): void;
protected client: Stan;
protected ackWait: number = 5000;
constructor(client: Stan);
listen(): void;
}Event Interfaces
ConnectionRequestedEvent
export interface ConnectionRequestedEvent {
subject: Subjects.ConnectionRequested;
data: {
postId: string;
postTitle: string;
postAuthorId: string;
requestedUserId: string;
requestedUserName: string;
requestedUserProfilePicture: string;
toLearn: string[];
toTeach: string[];
};
}PaymentCreatedEvent
export interface PaymentCreatedEvent {
subject: Subjects.PaymentCreated;
data: {
userId: string;
stripeId: string;
};
}ReviewCreatedEvent
export interface ReviewCreatedEvent {
subject: Subjects.ReviewCreated;
data: {
userId: string;
review: string;
rating: number;
};
}Error Handling
CustomError (Base Class)
export abstract class CustomError extends Error {
abstract statusCode: number;
abstract serializeErrors(): {
message: string;
field?: string;
}[];
}Specific Error Classes
BadRequestError
export class BadRequestError extends CustomError {
statusCode = 400;
constructor(public message: string);
serializeErrors(): { message: string; field?: string }[];
}NotFoundError
export class NotFoundError extends CustomError {
statusCode = 404;
constructor();
serializeErrors(): { message: string; field?: string }[];
}NotAuthenticatedError
export class NotAuthenticatedError extends CustomError {
statusCode = 401;
constructor();
serializeErrors(): { message: string; field?: string }[];
}RequestValidationError
export class RequestValidationError extends CustomError {
statusCode = 400;
constructor(public errs: ValidationError[]);
serializeErrors(): { message: string; field?: string }[];
}DatabaseError
export class DatabaseError extends CustomError {
statusCode = 500;
constructor(public reason: string = 'Error connecting to database');
serializeErrors(): { message: string; field?: string }[];
}Middleware
errorHandler
Global error handling middleware that processes all custom errors:
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => void;Features:
- Handles custom errors with appropriate status codes
- Logs unhandled errors with stack traces
- Returns standardized error responses
setCurrentUser
JWT authentication middleware that decodes and sets the current user:
export const setCurrentUser = (
req: Request,
res: Response,
next: NextFunction
) => void;Features:
- Decodes JWT from session cookies
- Sets
req.currentUserwith user information - Gracefully handles invalid tokens
requireAuth
Authorization middleware that ensures user authentication:
export const requireAuth = (
req: Request,
res: Response,
next: NextFunction
) => void;Features:
- Throws
NotAuthenticatedErrorif user is not authenticated - Must be used after
setCurrentUser
requestValidationHandler
Express-validator error processing middleware:
export const requestValidationHandler = (
req: Request,
res: Response,
next: NextFunction
) => void;Features:
- Processes express-validator results
- Throws
RequestValidationErrorfor validation failures
Types
User Interface (from setCurrentUser)
interface decodedUser {
email: string;
id: string;
profilePicture: string;
fullName: string;
isPremium: boolean;
}Order Status Enum
export enum OrderStatus {
Created = 'created',
Cancelled = 'cancelled',
AwatingPayment = 'awating:payment',
Complete = 'complete',
}Usage Examples
Creating a Publisher
import { Publisher, PaymentCreatedEvent, Subjects } from '@cse-350/shared-library';
import { natsWrapper } from '../nats-wrapper';
class PaymentCreatedPublisher extends Publisher<PaymentCreatedEvent> {
subject: Subjects.PaymentCreated = Subjects.PaymentCreated;
}
// Usage
const publisher = new PaymentCreatedPublisher(natsWrapper.client);
await publisher.publish({
userId: 'user123',
stripeId: 'stripe456'
});Creating a Listener
import { Listener, PaymentCreatedEvent, Subjects } from '@cse-350/shared-library';
import { Message } from 'node-nats-streaming';
class PaymentCreatedListener extends Listener<PaymentCreatedEvent> {
subject: Subjects.PaymentCreated = Subjects.PaymentCreated;
queueGroupName = 'auth-service';
async onMessage(data: PaymentCreatedEvent['data'], msg: Message) {
// Update user premium status
const user = await User.findById(data.userId);
if (user) {
user.isPremium = true;
await user.save();
}
msg.ack();
}
}Using Middleware
import express from 'express';
import {
errorHandler,
setCurrentUser,
requireAuth,
requestValidationHandler,
BadRequestError
} from '@cse-350/shared-library';
import { body } from 'express-validator';
const app = express();
// Global middleware
app.use(express.json());
app.use(setCurrentUser);
// Route with validation and authentication
app.post('/api/posts',
[
body('title').notEmpty().withMessage('Title is required'),
body('content').notEmpty().withMessage('Content is required')
],
requestValidationHandler,
requireAuth,
async (req, res) => {
const { title, content } = req.body;
if (!title || !content) {
throw new BadRequestError('Missing required fields');
}
// Create post logic
res.status(201).json({ message: 'Post created' });
}
);
// Global error handler (must be last)
app.use(errorHandler);Error Handling
import { BadRequestError, NotFoundError } from '@cse-350/shared-library';
// In your route handlers
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError();
}
res.json(user);
});
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
throw new BadRequestError('Email and password are required');
}
// Login logic
});Event-Driven Communication
Communication Flow
- Service A publishes an event using a Publisher
- NATS Streaming receives and stores the event
- Service B receives the event through a Listener
- Service B processes the event and acknowledges it
Event Patterns
Payment Processing
Payment Service ā PaymentCreated ā Auth Service
ā Community ServiceConnection Management
Community Service ā ConnectionRequested ā Connection Service
Connection Service ā ConnectionAccepted ā Community Service
ā Auth ServicePost Management
Community Service ā PostDeleted ā Connection ServiceReview System
Connection Service ā ReviewCreated ā Auth ServiceMessage Acknowledgment
All listeners must acknowledge messages to ensure delivery:
async onMessage(data: EventData, msg: Message) {
try {
// Process the event
await this.processEvent(data);
// Acknowledge successful processing
msg.ack();
} catch (error) {
console.error('Error processing event:', error);
// Don't acknowledge - message will be redelivered
}
}Best Practices
Event Design
- Immutable Events: Events should represent facts that have already happened
- Minimal Data: Include only essential data in events
- Backward Compatibility: Ensure new event versions don't break existing listeners
Error Handling
- Use Specific Errors: Choose the most appropriate error class
- Provide Clear Messages: Error messages should be user-friendly
- Log Detailed Errors: Include stack traces for debugging
Middleware Usage
- Order Matters: Apply middleware in the correct sequence
- Authentication First: Use
setCurrentUserbeforerequireAuth - Validation Early: Validate requests before business logic
Type Safety
- Extend Interfaces: Use proper TypeScript interfaces for events
- Generic Types: Leverage generics for reusable components
- Strict Typing: Enable strict TypeScript checking
Contributing
Development Setup
# Clone the repository
git clone <repository-url>
cd shared
# Install dependencies
npm install
# Build the library
npm run build
# Publish (requires permissions)
npm run pubPublishing Process
# Update version and publish
npm run pubThis will:
- Add changes to git
- Commit with "Updated modules" message
- Bump patch version
- Build the TypeScript
- Publish to npm
Testing
The shared library is tested through integration with the microservices. When making changes:
- Update the library
- Publish a new version
- Update dependencies in all services
- Run service test suites
Version History
- v1.0.10: Current stable version
- v1.0.x: Initial release with core functionality
Dependencies
{
"dependencies": {
"@types/cookie-session": "^2.0.49",
"@types/express": "^5.0.1",
"@types/jsonwebtoken": "^9.0.9",
"cookie-session": "^2.1.0",
"express": "^5.1.0",
"express-validator": "^7.2.1",
"jsonwebtoken": "^9.0.2",
"node-nats-streaming": "^0.3.2"
}
}License
ISC License - see package.json for details.
Support
For issues or questions:
- Create an issue in the SkillTrade repository
- Contact: [email protected]
Package URL: https://www.npmjs.com/package/@cse-350/shared-library
This shared library is the foundation that enables the SkillTrade microservices ecosystem to function cohesively while maintaining independence and scalability.