A robust Node.js backend service for the CustomThread application, built with Express.js and TypeScript. This service handles design management, user authentication, image processing, payment processing with Stripe, and sales/designer reporting.
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
# Start production server
npm startsrc/
βββ config/ # Configuration files
β βββ app.config.ts # Application configuration
β βββ database.ts # Database configuration
β βββ env.config.ts # Environment variables
β βββ logger.ts # Logging configuration
βββ controllers/ # Request handlers
β βββ v1/ # API version 1 controllers
β βββ design.controller.ts # Design management
β βββ order.controller.ts # Order & payment processing
β βββ report.controller.ts # Sales & designer reports
βββ middleware/ # Express middleware
β βββ auth.ts # Authentication middleware
β βββ auth.middleware.ts # New auth middleware
β βββ error.middleware.ts # Error handling
β βββ isAdmin.ts # Admin authorization
β βββ request-logger.middleware.ts # Request logging
β βββ validate-request.ts # Request validation
βββ models/ # MongoDB models
β βββ design.model.ts # Design data model
β βββ order.model.ts # Order data model
βββ routes/ # API routes
β βββ v1/ # API version 1 routes
β βββ design.routes.ts # Design endpoints
β βββ order.routes.ts # Order endpoints
β βββ report.routes.ts # Report endpoints
βββ services/ # Business logic
β βββ design.service.ts # Design service
β βββ order.service.ts # Order service
β βββ report.service.ts # Report service
βββ types/ # TypeScript type definitions
βββ validators/ # Request validators
βββ app.ts # Express app setup
βββ index.ts # Application entry point
Create a .env file in the root directory with the following variables:
# Server Configuration
NODE_ENV=development
PORT=3001
# Database
MONGODB_URI=your_mongodb_connection_string
# CORS Configuration (comma-separated list of allowed origins)
CORS_ORIGIN=http://localhost:5173,http://localhost:3000
# Cloudinary Configuration
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
# Clerk Authentication
CLERK_SECRET_KEY=your_clerk_secret_key
CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key
# Stripe Configuration
STRIPE_SECRET_KEY=your_stripe_secret_key
STRIPE_PUBLISHABLE_KEY=your_stripe_publishable_key
WEBHOOK_ENDPOINT_SECRET=your_stripe_webhook_secret
# Frontend URL (for success/cancel redirects)
FRONTEND_URL=http://localhost:5173
# Admin Configuration
ADMIN_USER_ID=your_admin_user_idWe use Clerk for authentication. The authentication flow works as follows:
- Frontend sends requests with a Bearer token in the Authorization header
- Our auth middleware validates the token using Clerk
- User information is attached to the request object as
req.auth - Protected routes can access user data via
req.auth.userId
The application provides two authentication middleware options:
// Example protected route using legacy middleware
router.post(
'/designs',
authenticate, // Clerk middleware
validateRequest(designValidation.createDesign),
designController.createDesign
);The authenticate middleware:
- Validates the Bearer token
- Extracts user information
- Attaches it to
req.auth - Rejects unauthorized requests with 401
// Example protected route using new middleware
router.get(
'/orders/user',
verifyAuth,
async (req: AuthRequest, res: Response, next: NextFunction) => {
// Access auth data via req.auth
const userId = req.auth?.userId;
// ...
}
);The verifyAuth middleware:
- Provides TypeScript type safety with
AuthRequestinterface - Validates the Bearer token
- Extracts user information
- Attaches it to
req.auth - Rejects unauthorized requests with 401
For admin-only routes, we use the isAdmin middleware:
// Example admin route
router.patch('/orders/:orderId/status', authenticate, isAdmin, orderController.updateOrderStatus);The isAdmin middleware:
- Checks if the authenticated user has admin privileges
- Allows the request to proceed if the user is an admin
- Returns a 403 Forbidden response if the user is not an admin
Some routes, like the sales report, are restricted to specific usernames:
// In the frontend component
if (user?.username === 'belazy167') {
// Allow access to sales report
} else {
// Redirect or show access denied
}- Authentication: Request must include a valid Clerk token
- Validation: Request body is validated against the schema
- User Association: Design is associated with authenticated user
- Image Processing: Images are stored in Cloudinary
- Database Storage: Design data is stored in MongoDB
interface DesignDetail {
title: string; // 3-100 characters
description: string; // Optional, max 1000 characters
tags: string[]; // 1-10 tags
color: string; // Hex color code
price: number; // Minimum 0
}
interface Design {
userId: string; // From auth token
username: string; // Username for display
designDetail: DesignDetail;
image: string; // Cloudinary URL
decal?: string; // Optional Cloudinary URL
}const designValidation = {
createDesign: {
body: {
userId: optional(), // Extracted from auth token
username: string().optional(),
designDetail: {
title: string().min(3).max(100),
description: string().max(1000).optional(),
tags: array(string()).min(1).max(10),
color: regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/),
price: number().min(0),
},
image: string().url(),
decal: string().url().optional(),
},
},
};- Cart Creation: User adds designs to their cart
- Checkout Initiation: User provides shipping details and initiates checkout
- Order Creation: Backend creates a pending order in the database
- Stripe Session: Backend creates a Stripe checkout session
- Payment Processing: User completes payment on Stripe-hosted page
- Webhook Handling: Stripe sends webhook events to update order status
- Order Confirmation: Order status is updated based on payment result
interface CheckoutSessionRequest {
items: {
designId: string;
quantity: number;
size: string;
customizations?: {
color?: string;
text?: string;
placement?: string;
};
}[];
shippingDetails: {
name: string;
email: string;
address: string;
address2?: string;
city: string;
state: string;
contact: string;
country: string;
postalCode: string;
shippingMethod?: string;
};
}The application integrates with Stripe for secure payment processing:
-
Checkout Session Creation:
- Creates a Stripe checkout session with product details
- Configures success and cancel URLs
- Stores order ID in session metadata
-
Webhook Handling:
- Listens for Stripe webhook events
- Processes
checkout.session.completedevents - Updates order status based on payment result
- Handles payment failures and successes
-
Testing Stripe Integration:
- Use Stripe test mode with test API keys
- Use test card numbers (e.g., 4242 4242 4242 4242 for success)
- Test webhooks locally using Stripe CLI
Administrators can manage orders through the API:
- View Orders: Get all orders or a specific order
- Update Status: Change order status (pending, confirmed, processing, shipped, delivered, cancelled)
- Order Tracking: Each order has a unique ID for tracking
CORS is configured to handle multiple origins securely:
{
origin: function(origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, origin);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['Content-Range', 'X-Content-Range'],
maxAge: 600 // 10 minutes
}- Configure allowed origins in your
.envfile:
CORS_ORIGIN=http://localhost:5173,http://localhost:3000- CORS error responses include:
{
"message": "CORS error: Origin not allowed",
"allowedOrigins": ["http://localhost:5173", "http://localhost:3000"]
}The application uses a comprehensive logging system built with Winston and Morgan:
{
requestId: string, // Unique identifier for request tracking
method: string, // HTTP method
url: string, // Request URL
ip: string, // Client IP
userAgent: string, // Client user agent
userId: string, // Authenticated user ID (if available)
body: object, // Request body (for non-GET requests)
query: object, // Query parameters
params: object, // URL parameters
duration: string, // Request duration
statusCode: number // Response status code
}{
error: string, // Error message
stack: string, // Error stack trace
method: string, // HTTP method
url: string, // Request URL
ip: string, // Client IP
userId: string, // User ID if authenticated
body: object, // Request body
query: object, // Query parameters
params: object // URL parameters
}- Daily Rotating Files:
logs/error-%DATE%.log: Error-level logslogs/combined-%DATE%.log: All logs
- Rotation Policy:
- Maximum file size: 10MB
- Retention period: 14 days
- Date pattern: YYYY-MM-DD
- Console output enabled with colors
- Debug level enabled
- Full error stacks
- Request body logging
- File-only logging
- Info level minimum
- Sanitized error messages
- Limited request body logging
import { logger } from './config/logger';
// Basic logging
logger.info('Server started');
// Logging with context
logger.info('User action', {
userId: 'user123',
action: 'create_design',
});
// Error logging
try {
// ... code
} catch (error) {
logger.error('Operation failed', {
error: error.message,
stack: error.stack,
});
}| Method | Endpoint | Description | Auth Required | Request Body | Response |
|---|---|---|---|---|---|
| GET | / | List all designs | No | - | { designs: Design[], total: number, page: number, totalPages: number } |
| POST | / | Create a design | Yes | DesignCreateInput |
Design |
| GET | /search | Search designs | No | - | { designs: Design[], total: number, page: number, totalPages: number } |
| GET | /random | Get random designs | No | - | { designs: Design[] } |
| GET | /user/:userId | Get designs by user | No | - | { designs: Design[], pagination: { total, page, limit, totalPages } } |
| GET | /:id | Get design by ID | No | - | Design |
| PATCH | /:id | Update design | Yes | DesignUpdateInput |
Design |
| DELETE | /:id | Delete design | Yes | - | { message: string } |
| Method | Endpoint | Description | Auth Required | Admin Only | Request Body | Response |
|---|---|---|---|---|---|---|
| GET | / | List all orders | Yes | No | - | { orders: Order[], pagination: {...} } |
| GET | /user | Get user's orders | Yes | No | - | { orders: Order[], pagination: {...} } |
| GET | /:orderId | Get order by ID | Yes | No | - | Order |
| POST | /checkout | Create checkout session | Yes | No | CheckoutSessionRequest |
{ url: string } |
| PATCH | /:orderId/cancel | Cancel an order | Yes | No | - | { message: string, order: Order } |
| PATCH | /:orderId/status | Update order status | Yes | Yes | { status: string } |
Order |
| POST | /webhook | Stripe webhook handler | No | No | Stripe Event | { received: true } |
| Method | Endpoint | Description | Auth Required | Admin Only | Request Body | Response |
|---|---|---|---|---|---|---|
| GET | /sales | Generate sales report | No* | No* | - | { summary: {...}, salesByDate: [...], ... } |
| GET | /designers/:designerId | Generate designer report | No* | No* | - | { summary: {...}, salesByDate: [...], ... } |
| GET | /designs/:designId | Generate design report | No* | No* | - | { summary: {...}, salesByDate: [...], ... } |
*Note: Authentication is temporarily disabled for development. In production, these endpoints will require authentication and proper authorization.
- Compression: Response compression using
compressionmiddleware - CORS Caching: CORS preflight requests are cached for 10 minutes
- MongoDB Indexes: Proper indexes for frequently queried fields
- Request Limiting: Large request bodies are limited to 50MB
- Image Optimization: Images are processed and optimized via Cloudinary
- Helmet: Security headers are set using
helmetmiddleware - CORS: Strict CORS policy with allowed origins
- Authentication: Clerk-based authentication with token validation
- Input Validation: Request validation using Zod
- Error Handling: Centralized error handling without exposing internals
- Rate Limiting: API rate limiting per IP and user
- Secure Headers: HTTP security headers configured
# Lint code
npm run lint
# Format code
npm run format
# Run tests
npm testFor detailed logging in development:
# Windows
set DEBUG=custom-thread:* & npm run dev
# Unix
DEBUG=custom-thread:* npm run dev- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
This project is licensed under the MIT License.