Claude-skill-registry-data mern-patterns
MERN stack patterns including React with Vite, Express middleware, MongoDB schemas, API Gateway architecture, session management, error handling, and testing strategies. Activate for MERN development, microservices architecture, and full-stack JavaScript applications.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry-data
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/mern-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-data-mern-patterns && rm -rf "$T"
manifest:
data/mern-patterns/SKILL.mdsource content
MERN Stack Patterns Skill
Comprehensive MERN stack development patterns for the keycloak-alpha multi-tenant platform with 8 microservices.
When to Use This Skill
Activate this skill when:
- Building React + Vite frontend applications
- Implementing Express microservices
- Designing MongoDB schemas and data models
- Setting up API Gateway architecture
- Implementing session and cookie management
- Adding error handling and validation
- Writing tests for MERN stack applications
keycloak-alpha Project Structure
keycloak-alpha/ ├── apps/ │ ├── web-app/ # Main React + Vite SPA │ │ ├── src/ │ │ │ ├── components/ │ │ │ ├── pages/ │ │ │ ├── hooks/ │ │ │ ├── contexts/ │ │ │ ├── config/ │ │ │ ├── utils/ │ │ │ └── main.jsx │ │ ├── vite.config.js │ │ └── package.json │ └── admin-portal/ # Admin dashboard (React + Vite) │ ├── services/ │ ├── api-gateway/ # Express API Gateway │ ├── user-service/ # User management │ ├── org-service/ # Organization management │ ├── tenant-service/ # Multi-tenant provisioning │ ├── notification-service/ # Email/SMS notifications │ ├── billing-service/ # Stripe integration │ ├── analytics-service/ # Usage analytics │ └── keycloak-service/ # Keycloak integration │ ├── routes/ │ ├── api/ │ │ ├── users.js │ │ ├── organizations.js │ │ └── tenants.js │ └── index.js │ ├── shared/ │ ├── types/ │ ├── utils/ │ └── constants/ │ └── package.json
React + Vite Frontend Patterns
Vite Configuration
// apps/web-app/vite.config.js import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@hooks': path.resolve(__dirname, './src/hooks'), '@contexts': path.resolve(__dirname, './src/contexts'), '@utils': path.resolve(__dirname, './src/utils'), '@config': path.resolve(__dirname, './src/config'), } }, server: { port: 3000, proxy: { '/api': { target: 'http://localhost:4000', changeOrigin: true, } } }, build: { outDir: 'dist', sourcemap: true, rollupOptions: { output: { manualChunks: { 'vendor': ['react', 'react-dom', 'react-router-dom'], 'keycloak': ['keycloak-js'], 'ui': ['@chakra-ui/react', '@emotion/react'] } } } }, optimizeDeps: { include: ['react', 'react-dom', 'keycloak-js'] } });
Component Organization
// apps/web-app/src/components/features/UserProfile/index.jsx import { useState, useEffect } from 'react'; import { useAuth } from '@hooks/useAuth'; import { useToast } from '@chakra-ui/react'; import { updateUserProfile } from '@/api/users'; export function UserProfile() { const { user } = useAuth(); const toast = useToast(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { fetchProfile(); }, []); async function fetchProfile() { setLoading(true); try { const data = await getUserProfile(user.sub); setProfile(data); } catch (error) { toast({ title: 'Error loading profile', description: error.message, status: 'error', duration: 5000, }); } finally { setLoading(false); } } async function handleSubmit(formData) { try { await updateUserProfile(user.sub, formData); toast({ title: 'Profile updated', status: 'success', duration: 3000, }); } catch (error) { toast({ title: 'Update failed', description: error.message, status: 'error', duration: 5000, }); } } if (loading) return <Spinner />; if (!profile) return <Alert>Profile not found</Alert>; return <ProfileForm profile={profile} onSubmit={handleSubmit} />; }
Custom Hooks Pattern
// apps/web-app/src/hooks/useAuth.js import { createContext, useContext, useState, useEffect } from 'react'; import Keycloak from 'keycloak-js'; import { keycloakConfig } from '@config/keycloak.config'; const AuthContext = createContext(null); export function AuthProvider({ children }) { const [keycloak, setKeycloak] = useState(null); const [authenticated, setAuthenticated] = useState(false); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const kc = new Keycloak(keycloakConfig); kc.init({ onLoad: 'check-sso', checkLoginIframe: true, pkceMethod: 'S256' }).then(authenticated => { setKeycloak(kc); setAuthenticated(authenticated); if (authenticated) { setUser({ sub: kc.tokenParsed.sub, email: kc.tokenParsed.email, name: kc.tokenParsed.name, orgId: kc.tokenParsed.org_id, roles: kc.tokenParsed.realm_access?.roles || [] }); } setLoading(false); }); // Token refresh kc.onTokenExpired = () => { kc.updateToken(30).catch(() => { kc.logout(); }); }; }, []); const login = () => keycloak.login(); const logout = () => keycloak.logout(); const getToken = () => keycloak.token; return ( <AuthContext.Provider value={{ authenticated, user, loading, login, logout, getToken }}> {children} </AuthContext.Provider> ); } export const useAuth = () => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; };
API Client Pattern
// apps/web-app/src/utils/apiClient.js import axios from 'axios'; import { useAuth } from '@hooks/useAuth'; const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:4000/api', timeout: 10000, headers: { 'Content-Type': 'application/json' } }); // Request interceptor to add auth token apiClient.interceptors.request.use( async (config) => { const token = await getToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // Response interceptor for error handling apiClient.interceptors.response.use( (response) => response.data, (error) => { if (error.response?.status === 401) { // Redirect to login window.location.href = '/login'; } const errorMessage = error.response?.data?.message || error.message; return Promise.reject(new Error(errorMessage)); } ); export default apiClient; // Typed API methods export const api = { users: { getProfile: (userId) => apiClient.get(`/users/${userId}`), updateProfile: (userId, data) => apiClient.put(`/users/${userId}`, data), listUsers: (orgId) => apiClient.get(`/users?org_id=${orgId}`) }, organizations: { get: (orgId) => apiClient.get(`/organizations/${orgId}`), create: (data) => apiClient.post('/organizations', data), update: (orgId, data) => apiClient.put(`/organizations/${orgId}`, data) } };
Express Microservice Patterns
Service Structure
// services/user-service/src/index.js import express from 'express'; import helmet from 'helmet'; import cors from 'cors'; import morgan from 'morgan'; import { connectDB } from './config/database.js'; import { errorHandler } from './middleware/errorHandler.js'; import { authMiddleware } from './middleware/auth.js'; import userRoutes from './routes/users.js'; const app = express(); // Security middleware app.use(helmet()); app.use(cors({ origin: process.env.CORS_ORIGIN?.split(',') || 'http://localhost:3000', credentials: true })); // Logging app.use(morgan('combined')); // Body parsing app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); // Connect to MongoDB await connectDB(); // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'user-service' }); }); // API routes (protected) app.use('/api/users', authMiddleware, userRoutes); // Error handling app.use(errorHandler); const PORT = process.env.PORT || 5001; app.listen(PORT, () => { console.log(`User service running on port ${PORT}`); });
Route Organization
// services/user-service/src/routes/users.js import express from 'express'; import { body, param, query } from 'express-validator'; import { validate } from '../middleware/validate.js'; import { requireRole } from '../middleware/rbac.js'; import * as userController from '../controllers/user.controller.js'; const router = express.Router(); // GET /api/users - List users (org admin only) router.get('/', requireRole(['org_admin']), query('org_id').optional().isString(), query('page').optional().isInt({ min: 1 }), query('limit').optional().isInt({ min: 1, max: 100 }), validate, userController.listUsers ); // GET /api/users/:id - Get user by ID router.get('/:id', param('id').isUUID(), validate, userController.getUser ); // POST /api/users - Create user (org admin only) router.post('/', requireRole(['org_admin']), body('email').isEmail().normalizeEmail(), body('firstName').trim().isLength({ min: 1, max: 50 }), body('lastName').trim().isLength({ min: 1, max: 50 }), body('orgId').isString(), validate, userController.createUser ); // PUT /api/users/:id - Update user router.put('/:id', param('id').isUUID(), body('firstName').optional().trim().isLength({ min: 1, max: 50 }), body('lastName').optional().trim().isLength({ min: 1, max: 50 }), validate, userController.updateUser ); // DELETE /api/users/:id - Delete user (org admin only) router.delete('/:id', requireRole(['org_admin']), param('id').isUUID(), validate, userController.deleteUser ); export default router;
Controller Pattern
// services/user-service/src/controllers/user.controller.js import { UserModel } from '../models/User.js'; import { KeycloakService } from '../services/keycloak.service.js'; import { AppError } from '../utils/AppError.js'; export async function listUsers(req, res, next) { try { const { org_id, page = 1, limit = 20 } = req.query; // Ensure user can only list users from their org (unless super admin) const orgIdFilter = req.user.roles.includes('super_admin') ? org_id : req.user.org_id; if (!orgIdFilter) { throw new AppError('Organization ID required', 400); } const users = await UserModel.find({ org_id: orgIdFilter }) .select('-password') .limit(limit) .skip((page - 1) * limit) .sort({ createdAt: -1 }); const total = await UserModel.countDocuments({ org_id: orgIdFilter }); res.json({ users, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); } catch (error) { next(error); } } export async function getUser(req, res, next) { try { const { id } = req.params; const user = await UserModel.findById(id).select('-password'); if (!user) { throw new AppError('User not found', 404); } // Ensure user can only access users from their org if (user.org_id !== req.user.org_id && !req.user.roles.includes('super_admin')) { throw new AppError('Access denied', 403); } res.json(user); } catch (error) { next(error); } } export async function createUser(req, res, next) { try { const { email, firstName, lastName, orgId } = req.body; // Verify org_id matches user's org (unless super admin) if (orgId !== req.user.org_id && !req.user.roles.includes('super_admin')) { throw new AppError('Cannot create user for different organization', 403); } // Create user in Keycloak const keycloakService = new KeycloakService(); const keycloakUserId = await keycloakService.createUser({ email, firstName, lastName, orgId }); // Create user in MongoDB const user = new UserModel({ keycloakId: keycloakUserId, email, firstName, lastName, org_id: orgId, createdBy: req.user.sub }); await user.save(); res.status(201).json({ id: user._id, keycloakId: keycloakUserId, email: user.email }); } catch (error) { next(error); } }
MongoDB Schema Patterns
User Model
// services/user-service/src/models/User.js import mongoose from 'mongoose'; const userSchema = new mongoose.Schema({ keycloakId: { type: String, required: true, unique: true, index: true }, email: { type: String, required: true, lowercase: true, trim: true, index: true }, firstName: { type: String, required: true, trim: true }, lastName: { type: String, required: true, trim: true }, org_id: { type: String, required: true, index: true }, roles: [{ type: String, enum: ['org_admin', 'org_user', 'super_admin'] }], metadata: { type: Map, of: String }, createdBy: String, updatedBy: String }, { timestamps: true, toJSON: { transform: (doc, ret) => { ret.id = ret._id; delete ret._id; delete ret.__v; return ret; } } }); // Compound index for org queries userSchema.index({ org_id: 1, email: 1 }, { unique: true }); // Virtual for full name userSchema.virtual('fullName').get(function() { return `${this.firstName} ${this.lastName}`; }); // Pre-save hook userSchema.pre('save', function(next) { if (this.isModified('email')) { this.email = this.email.toLowerCase(); } next(); }); export const UserModel = mongoose.model('User', userSchema);
Organization Model
// services/org-service/src/models/Organization.js import mongoose from 'mongoose'; const organizationSchema = new mongoose.Schema({ org_id: { type: String, required: true, unique: true, index: true }, name: { type: String, required: true, trim: true }, domain: { type: String, required: true, unique: true, lowercase: true }, settings: { theme: { type: String, default: 'lobbi-base' }, features: { type: Map, of: Boolean, default: new Map() }, branding: { logoUrl: String, primaryColor: String, secondaryColor: String } }, subscription: { plan: { type: String, enum: ['free', 'starter', 'professional', 'enterprise'], default: 'free' }, status: { type: String, enum: ['active', 'inactive', 'suspended'], default: 'active' }, billingCycle: { type: String, enum: ['monthly', 'annual'] }, stripeCustomerId: String, stripeSubscriptionId: String }, adminUsers: [{ userId: String, email: String, addedAt: Date }], status: { type: String, enum: ['active', 'inactive', 'suspended'], default: 'active' } }, { timestamps: true }); export const OrganizationModel = mongoose.model('Organization', organizationSchema);
API Gateway Architecture
Gateway Setup
// services/api-gateway/src/index.js import express from 'express'; import { createProxyMiddleware } from 'http-proxy-middleware'; import { authMiddleware } from './middleware/auth.js'; import { rateLimiter } from './middleware/rateLimit.js'; import { cacheMiddleware } from './middleware/cache.js'; const app = express(); // Rate limiting app.use(rateLimiter); // Authentication app.use(authMiddleware); // Service routing const services = { users: process.env.USER_SERVICE_URL || 'http://localhost:5001', orgs: process.env.ORG_SERVICE_URL || 'http://localhost:5002', tenants: process.env.TENANT_SERVICE_URL || 'http://localhost:5003', notifications: process.env.NOTIFICATION_SERVICE_URL || 'http://localhost:5004', billing: process.env.BILLING_SERVICE_URL || 'http://localhost:5005', analytics: process.env.ANALYTICS_SERVICE_URL || 'http://localhost:5006' }; // Proxy to microservices Object.entries(services).forEach(([name, target]) => { app.use(`/api/${name}`, createProxyMiddleware({ target, changeOrigin: true, pathRewrite: { [`^/api/${name}`]: '' }, onProxyReq: (proxyReq, req) => { // Forward user context if (req.user) { proxyReq.setHeader('X-User-Id', req.user.sub); proxyReq.setHeader('X-Org-Id', req.user.org_id); proxyReq.setHeader('X-User-Roles', JSON.stringify(req.user.roles)); } } })); }); app.listen(4000, () => { console.log('API Gateway running on port 4000'); });
Session and Cookie Management
Session Configuration
// services/api-gateway/src/config/session.js import session from 'express-session'; import MongoStore from 'connect-mongo'; export const sessionConfig = session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, store: MongoStore.create({ mongoUrl: process.env.MONGODB_URL, ttl: 24 * 60 * 60, // 1 day touchAfter: 24 * 3600 // lazy session update }), cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 1000 * 60 * 60 * 24, // 24 hours sameSite: 'lax', domain: process.env.COOKIE_DOMAIN }, name: 'lobbi.sid' });
Error Handling Patterns
Custom Error Classes
// shared/utils/AppError.js export class AppError extends Error { constructor(message, statusCode = 500, isOperational = true) { super(message); this.statusCode = statusCode; this.isOperational = isOperational; Error.captureStackTrace(this, this.constructor); } } export class ValidationError extends AppError { constructor(message, errors = []) { super(message, 400); this.errors = errors; } } export class NotFoundError extends AppError { constructor(resource) { super(`${resource} not found`, 404); } } export class UnauthorizedError extends AppError { constructor(message = 'Unauthorized') { super(message, 401); } } export class ForbiddenError extends AppError { constructor(message = 'Forbidden') { super(message, 403); } }
Error Handler Middleware
// services/user-service/src/middleware/errorHandler.js import { AppError } from '../utils/AppError.js'; export function errorHandler(err, req, res, next) { let { statusCode, message, isOperational } = err; // Default to 500 server error if (!statusCode) { statusCode = 500; isOperational = false; } // Log error console.error('Error:', { message, statusCode, isOperational, stack: err.stack, url: req.url, method: req.method, user: req.user?.sub }); // Send error response res.status(statusCode).json({ error: { message: isOperational ? message : 'Internal server error', statusCode, ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) } }); } // Async error wrapper export function asyncHandler(fn) { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; }
Testing Strategies
Unit Tests with Jest
// services/user-service/tests/controllers/user.controller.test.js import { listUsers, createUser } from '../../src/controllers/user.controller.js'; import { UserModel } from '../../src/models/User.js'; import { KeycloakService } from '../../src/services/keycloak.service.js'; jest.mock('../../src/models/User.js'); jest.mock('../../src/services/keycloak.service.js'); describe('UserController', () => { describe('listUsers', () => { it('should return paginated users for org', async () => { const mockUsers = [ { _id: '1', email: 'user1@test.com', org_id: 'org_1' }, { _id: '2', email: 'user2@test.com', org_id: 'org_1' } ]; UserModel.find.mockReturnValue({ select: jest.fn().mockReturnThis(), limit: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), sort: jest.fn().mockResolvedValue(mockUsers) }); UserModel.countDocuments.mockResolvedValue(2); const req = { query: { org_id: 'org_1', page: 1, limit: 20 }, user: { org_id: 'org_1', roles: ['org_admin'] } }; const res = { json: jest.fn() }; const next = jest.fn(); await listUsers(req, res, next); expect(res.json).toHaveBeenCalledWith({ users: mockUsers, pagination: expect.objectContaining({ page: 1, total: 2 }) }); }); }); });
Integration Tests
// services/user-service/tests/integration/users.test.js import request from 'supertest'; import { app } from '../../src/index.js'; import { connectDB, closeDB, clearDB } from '../setup.js'; beforeAll(async () => await connectDB()); afterEach(async () => await clearDB()); afterAll(async () => await closeDB()); describe('User API Integration', () => { it('POST /api/users - should create user', async () => { const userData = { email: 'test@example.com', firstName: 'Test', lastName: 'User', orgId: 'org_test' }; const response = await request(app) .post('/api/users') .set('Authorization', `Bearer ${mockAdminToken}`) .send(userData) .expect(201); expect(response.body).toMatchObject({ email: userData.email }); }); });
Best Practices
- Use environment variables for all configuration
- Implement proper error handling with custom error classes
- Validate all inputs using express-validator or Joi
- Use async/await consistently, avoid callback hell
- Implement proper logging with structured logs
- Use MongoDB indexes for frequently queried fields
- Implement rate limiting to prevent abuse
- Use CORS properly with specific origins
- Implement request/response compression with gzip
- Use TypeScript for better type safety (optional)
- Implement health checks for all services
- Use connection pooling for database connections
- Implement graceful shutdown for services
- Use dependency injection for better testability
- Implement proper security headers with Helmet
File Locations in keycloak-alpha
| Path | Purpose |
|---|---|
| React + Vite main application |
| API Gateway with routing |
| User management microservice |
| Organization management |
| Shared route definitions |
| Shared utilities and helpers |