devware-commons
v0.0.22
Published
Common utilities and services for DevWare projects
Downloads
90
Readme
DevWare Commons
A comprehensive NestJS library providing essential modules and utilities for building modern applications. This package includes authentication, email/SMS services, AWS Parameter Store integration, and common utilities to accelerate development across DevWare projects.
Features
- 🔐 Authentication Module - JWT-based authentication with session management
- 📧 Mail Service - AWS SES integration for sending emails
- 📱 SMS Service - Twilio integration for sending text messages
- Files Module - AWS S3 integration for secure file management with presigned URLs
- 🔧 Parameter Store - AWS Systems Manager Parameter Store client
- 🛠️ Common Utilities - Validators, pipes, interceptors, and helpers
Installation
npm install devware-commonsQuick Start
Import Individual Modules
// Import specific modules (recommended)
import { AuthModule } from 'devware-commons/auth';
import { MailModule } from 'devware-commons/mail';
import { SmsModule } from 'devware-commons/sms';
import { FilesModule } from 'devware-commons/files';
import { ValidateMongoIdPipe } from 'devware-commons/common';Or Import Everything
// Import everything from root (for backward compatibility)
import {
AuthModule,
MailService,
SmsService,
FilesService,
} from 'devware-commons';Modules
🔐 Authentication Module
Provides JWT-based authentication with refresh token support and MongoDB session storage.
Configuration
import { Module } from '@nestjs/common';
import { AuthModule } from 'devware-commons/auth';
import { UserService } from './user.service';
@Module({
imports: [
AuthModule.forRoot({
params: (injectedService: UsersService) => ({
jwtSecret: 'your-jwt-secret',
refreshTokenSecret: 'your-refresh-secret',
userService: injectedService,
}),
options: {
inject: [UsersService],
imports: [UsersModule],
},
}),
],
})
export class AppModule {}Configuration Parameters
| Parameter | Type | Required | Description |
| -------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| jwtSecret | string | ✅ | Secret key for JWT token signing |
| refreshTokenSecret | string | ✅ | Secret key for refresh token signing |
| userService | any | ✅ | Your user service implementation, user service should include the methods userForAuth(id), checkIsAdmin(user) and validateUser(username, password) |
Exports
AuthService- Authentication service for login/logout operationsJwtAuthGuard- Guard for protecting routes with JWT authenticationSessionServiceOptions- Interface for configuration options
📧 Mail Module
AWS SES integration for sending HTML emails with validation and error handling.
Configuration
import { MailModule } from 'devware-commons/mail';
@Module({
imports: [
MailModule.forRoot({
params: {
region: 'us-east-1', // AWS region
from: '[email protected]', // Default sender (optional)
},
options: {
isGlobal: true, // Make service globally available (optional)
},
}),
],
})
export class AppModule {}Configuration Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------- |
| region | string | ✅ | AWS region where SES is configured |
| from | string | ❌ | Default sender email address |
Usage
import { MailService, SendEmailDto } from 'devware-commons/mail';
@Injectable()
export class ExampleService {
constructor(private mailService: MailService) {}
async sendWelcomeEmail(userEmail: string) {
const result = await this.mailService.sendEmail({
to: userEmail,
subject: 'Welcome to Our Platform',
body: '<h1>Welcome!</h1><p>Thank you for joining us.</p>',
from: '[email protected]', // Optional, uses default if not provided
});
}
}Email Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------- |
| to | string | ✅ | Recipient email address |
| subject | string | ✅ | Email subject (max 200 chars) |
| body | string | ✅ | HTML email content (max 10,000 chars) |
| from | string | ❌ | Sender email (uses default if not provided) |
📱 SMS Module
Twilio integration for sending text messages with validation and error handling.
Configuration
import { SmsModule } from 'devware-commons/sms';
@Module({
imports: [
SmsModule.forRoot({
params: {
phoneNumber: '+1234567890', // Your Twilio phone number
accountSid: 'your-account-sid',
authToken: 'your-auth-token',
},
options: {
isGlobal: true, // Make service globally available (optional)
},
}),
],
})
export class AppModule {}Configuration Parameters
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ----------------------------------- |
| phoneNumber | string | ✅ | Twilio phone number for sending SMS |
| accountSid | string | ✅ | Twilio Account SID |
| authToken | string | ✅ | Twilio Auth Token |
Usage
import { SmsService, SendSmsDto } from 'devware-commons/sms';
@Injectable()
export class ExampleService {
constructor(private smsService: SmsService) {}
async sendVerificationCode(phoneNumber: string, code: string) {
const response = await this.smsService.sendSMS({
to: phoneNumber,
message: `Your verification code is: ${code}`,
});
return response;
}
}SMS Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ---------------------- |
| to | string | ✅ | Recipient phone number |
| message | string | ✅ | Text message content |
� Files Module
AWS S3 integration for secure file management with MongoDB metadata storage and presigned URL generation.
Configuration
import { FilesModule } from 'devware-commons/files';
@Module({
imports: [
FilesModule.forRoot({
params: {
region: 'us-east-1', // AWS region
publicBucketName: 'my-public-bucket', // Optional: for public files
privateBucketName: 'my-private-bucket', // Optional: for private files
},
options: {
isGlobal: true, // Make service globally available (optional)
},
}),
],
})
export class AppModule {}Configuration Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------ | -------- | --------------------------------------- |
| region | string | ✅ | AWS region where S3 buckets are located |
| publicBucketName | string | ❌ | S3 bucket name for public files |
| privateBucketName | string | ❌ | S3 bucket name for private files |
Note: At least one bucket (public or private) must be configured.
Usage
import { FilesService, FileDataDto, FileStatus } from 'devware-commons/files';
@Injectable()
export class ExampleService {
constructor(private filesService: FilesService) {}
// Upload a file (creates record and returns presigned URL)
async uploadFile() {
const fileData: FileDataDto = {
name: 'document.pdf',
size: 1024000, // size in bytes
mimeType: 'application/pdf',
extension: '.pdf',
isPublic: false, // true for public bucket, false for private
};
const result = await this.filesService.fileUpload(fileData);
// result contains:
// - file: MongoDB document with metadata
// - signedUrl: Presigned URL for S3 upload (expires in 520 seconds)
return result;
}
// Retrieve multiple files by IDs
async getFiles(fileIds: string[]) {
const files = await this.filesService.getFiles({ fileIds });
// Returns array of files with id, name, and S3 details
return files;
}
// Update file status
async publishFile(fileId: string) {
const updatedFile = await this.filesService.changeFileStatus(
fileId,
FileStatus.ACTIVE,
);
return updatedFile;
}
}File Upload DTOs
FileDataDto
| Parameter | Type | Required | Description |
| ----------- | ------- | -------- | ------------------------------------------------- |
| name | string | ✅ | Original filename |
| size | number | ✅ | File size in bytes |
| mimeType | string | ✅ | MIME type (e.g., 'image/jpeg', 'application/pdf') |
| extension | string | ✅ | File extension (e.g., '.jpg', '.pdf') |
| isPublic | boolean | ✅ | Upload to public (true) or private (false) bucket |
File Status Enum
enum FileStatus {
DRAFT = 'DRAFT', // Initial status after upload
ACTIVE = 'ACTIVE', // File is active and available
DELETED = 'DELETED', // File is marked as deleted
}Response Interfaces
File Upload Response
{
file: {
_id: string;
fileMeta: {
name: string;
size: number;
mimeType: string;
extension: string;
}
s3: {
bucket: string;
key: string; // unique filename generated
}
status: FileStatus;
createdAt: Date;
updatedAt: Date;
}
signedUrl: string; // Presigned URL for S3 upload (expires in 520 seconds)
}File Retrieve Response
{
id: string;
name: string;
s3: {
bucket: string;
key: string;
}
}Complete File Upload Flow
- Create File Record: Call
fileUpload()with file metadata - Upload to S3: Use the returned presigned URL to upload file to S3
- Update Status: Change status from DRAFT to ACTIVE after successful upload
- Retrieve Files: Use
getFiles()to fetch file information when needed
// Step 1: Create file record and get presigned URL
const { file, signedUrl } = await this.filesService.fileUpload({
name: 'profile-picture.jpg',
size: 524288, // 512KB
mimeType: 'image/jpeg',
extension: '.jpg',
isPublic: true,
});
// Step 2: Upload file to S3 using the presigned URL
// (This happens on the client side or using HTTP client)
const uploadResponse = await fetch(signedUrl, {
method: 'PUT',
body: fileBuffer,
headers: {
'Content-Type': 'image/jpeg',
},
});
// Step 3: Update file status after successful upload
if (uploadResponse.ok) {
await this.filesService.changeFileStatus(file._id, FileStatus.ACTIVE);
}Controller Integration
The module includes a built-in controller with the following endpoint:
POST / files / upload;Request Body:
{
"name": "document.pdf",
"size": 1024000,
"mimeType": "application/pdf",
"extension": ".pdf",
"isPublic": false
}Response:
{
"file": {
"_id": "64f8a1b2c3d4e5f6a7b8c9d0",
"fileMeta": {
"name": "document.pdf",
"size": 1024000,
"mimeType": "application/pdf",
"extension": ".pdf"
},
"s3": {
"bucket": "my-private-bucket",
"key": "1698765432000_abc123_document.pdf"
},
"status": "DRAFT",
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
},
"signedUrl": "https://s3.amazonaws.com/my-private-bucket/1698765432000_abc123_document.pdf?..."
}🔧 Parameter Store Service
AWS Systems Manager Parameter Store client for retrieving configuration parameters.
Usage
import { ParameterStoreService } from 'devware-commons/parameter-store';
@Injectable()
export class ConfigService {
async loadConfiguration() {
try {
const parameters = await ParameterStoreService.getParameters({
region: 'us-east-1',
environment: 'production',
projectName: 'my-app',
parameterNames: ['database-url', 'api-key', 'secret-key'],
});
console.log('Database URL:', parameters['database-url']);
console.log('API Key:', parameters['api-key']);
console.log('Secret Key:', parameters['secret-key']);
return parameters;
} catch (error) {
console.error('Failed to load parameters:', error.message);
throw error;
}
}
}Configuration Parameters
| Parameter | Type | Required | Description |
| ---------------- | -------- | -------- | -------------------------------------------- |
| region | string | ✅ | AWS region where parameters are stored |
| environment | string | ✅ | Environment identifier (staging, production) |
| projectName | string | ✅ | Project name used in parameter paths |
| parameterNames | string[] | ✅ | Array of parameter names to retrieve |
Retrieve All Parameters by Path
The getAllByPath method retrieves all parameters under a specific path hierarchy in AWS Parameter Store. This is useful when you want to load all configuration parameters for a given path without specifying individual parameter names.
import { ParameterStoreService } from 'devware-commons/parameter-store';
@Injectable()
export class ConfigService {
async loadAllConfigurationsByPath() {
try {
const parameters = await ParameterStoreService.getAllByPath(
'/production/my-app',
{
region: 'us-east-1',
recursive: true, // Optional: retrieve nested paths (default: true)
decrypt: true, // Optional: decrypt SecureString parameters (default: true)
},
);
// Returns object with parameter names (without path) as keys
// e.g., { 'database-url': 'value', 'api-key': 'value', 'secret-key': 'value' }
console.log('All parameters:', parameters);
return parameters;
} catch (error) {
console.error('Failed to load parameters by path:', error.message);
throw error;
}
}
}getAllByPath Parameters
| Parameter | Type | Required | Description |
| ----------- | ------- | -------- | ----------------------------------------------------------------- |
| path | string | ✅ | The path prefix for parameters (e.g., '/production/my-app') |
| region | string | ✅ | AWS region where parameters are stored |
| recursive | boolean | ❌ | Retrieve all parameters within the path hierarchy (default: true) |
| decrypt | boolean | ❌ | Decrypt SecureString parameters (default: true) |
Parameter Naming Convention
Parameters should be stored in AWS Parameter Store with the following path structure:
/{environment}/{projectName}/{parameterName}Example:
/production/my-app/database-url/production/my-app/api-key/staging/my-app/database-url
🛠️ Common Utilities
Collection of useful utilities, pipes, interceptors, and validators.
🔑 Password Service
The PasswordService provides secure password reset functionality, including generating unique password reset requests, validating them, and invalidating used or expired requests. It is designed to work with MongoDB and integrates with NestJS dependency injection.
Features
- Request Password Change: Generates a unique, expiring password reset request for a given email address.
- Validate Request: Checks if a password reset request is valid (active and not expired).
- Invalidate Request: Marks a password reset request as inactive after use.
Usage Example
import { PasswordService } from 'devware-commons/auth';
@Injectable()
export class AuthService {
constructor(private passwordService: PasswordService) {}
async initiatePasswordReset(email: string) {
const uniqueId = await this.passwordService.requestChangePassword({
email,
expDurationInHours: 2, // Optional, defaults to 24 hours
});
// Send uniqueId to user via email
return uniqueId;
}
async validateResetRequest(uniqueId: string) {
const request =
await this.passwordService.validatePasswordRequest(uniqueId);
if (!request) throw new Error('Invalid or expired request');
return request;
}
async completeReset(id: string) {
await this.passwordService.invalidateRequest(id);
}
}Methods
| Method | Description |
| ------------------------------- | --------------------------------------------------------- |
| requestChangePassword(params) | Creates a password reset request and returns a unique ID. |
| validatePasswordRequest(id) | Validates if a reset request is active and not expired. |
| invalidateRequest(id) | Marks a reset request as inactive after use. |
Model
The service uses a ChangePasswordRequest Mongoose model with fields:
email: User's email addressexpiration: Expiry date/timeuniqueId: Unique reset tokenactive: Boolean (true if request is valid)
Exports
import {
ValidateMongoIdPipe,
TransformInterceptor,
validatorDto,
} from 'devware-commons/common';ValidateMongoIdPipe
Validates MongoDB ObjectId format in route parameters.
@Controller('users')
export class UsersController {
@Get(':id')
async findOne(@Param('id', ValidateMongoIdPipe) id: string) {
// id is guaranteed to be a valid MongoDB ObjectId
return this.usersService.findOne(id);
}
}TransformInterceptor
Standardizes API responses with success/error format.
app.useGlobalInterceptors(new TransformInterceptor());Response Interface
Standard response interface for consistent API responses.
import { Response } from 'devware-commons/common';
// Success response
const successResponse: Response<User> = {
success: true,
data: user,
};
// Error response
const errorResponse: Response<null> = {
success: false,
status: 400,
error: { message: 'Invalid input' },
data: null,
};validatorDto
Utility function for validating DTOs using class-validator.
import { validatorDto } from 'devware-commons/common';
class CreateUserDto {
@IsEmail()
email: string;
@IsString()
name: string;
}
async function createUser(data: any) {
const validData = await validatorDto(CreateUserDto, data);
// validData is now validated and typed
}Phone validation REGEX
US_PHONE_NUMBER_REGEX;Phone validation REGEX
const password = hashPassword(
password,
saltOrRounds, // this is optional value: 10
);Advanced Configuration
Dynamic Configuration with Dependency Injection
You can inject dependencies into module configurations:
@Module({
imports: [
MailModule.forRoot({
params: (configService: ConfigService) => ({
region: configService.get('AWS_REGION'),
from: configService.get('DEFAULT_FROM_EMAIL'),
}),
options: {
inject: [ConfigService],
imports: [ConfigModule],
},
}),
],
})
export class AppModule {}