Building Secure, Real-Time Microservices with Moleculer, Cloudflare, and TypeScript
Modern web applications demand high scalability, real-time interactions, and robust security. Integrating a microservices framework like Moleculer with a global network like Cloudflare provides a powerful stack for achieving these goals, especially when combined with secure authentication and real-time WebSockets. This post demonstrates how to weave these technologies into a cohesive, TypeScript-driven application.
Architecture Overview
Our architecture centers around Moleculer microservices handling business logic and real-time events. Cloudflare acts as a protective shield and performance enhancer, proxying all traffic, including WebSockets, to our backend. Authentication and authorization secure access, while WebSockets facilitate instant communication.
Moleculer Microservices with TypeScript
Moleculer is a fast, modern, and powerful microservices framework for Node.js, providing features like service discovery, load balancing, and event-driven communication. We'll use TypeScript for type safety and enhanced developer experience.
// services/users.service.ts
import { Service, ServiceBroker } from "moleculer";
interface User { id: string; username: string; email: string; }
interface AuthenticatedUser { userId: string; username: string; token: string; }
export default class UsersService extends Service {
public constructor(broker: ServiceBroker) {
super(broker);
this.parseServiceSchema({
name: "users",
actions: {
// Authenticate a user and return a JWT token
login: {
params: {
username: "string",
password: "string",
},
async handler(ctx): Promise<AuthenticatedUser | null> {
// In a real app, validate credentials against a DB
const { username, password } = ctx.params;
if (username === "admin" && password === "password") {
const token = await this.broker.call("auth.generateToken", { userId: "123", roles: ["admin"] });
return { userId: "123", username, token };
}
return null;
},
},
// Get user profile (requires authentication)
profile: {
auth: "required", // Custom metadata for auth middleware
async handler(ctx): Promise<User> {
const { userId } = ctx.meta.user; // User from auth middleware
// Fetch user data from DB
return { id: userId, username: "admin", email: "admin@example.com" };
},
},
},
events: {
"user.created": {
handler(ctx) {
this.logger.info(`New user created: ${ctx.params.username}`);
},
},
},
});
}
}
The users service handles user-related actions, including a login action to simulate authentication and a profile action requiring authorization.
Authentication & Authorization
We implement JWT-based authentication using a dedicated auth service and Moleculer middleware for validation. Authorization is handled via role-based access control (RBAC) within service actions.
// services/auth.service.ts
import { Service, ServiceBroker } from "moleculer";
import * as jwt from "jsonwebtoken";
const JWT_SECRET = process.env.JWT_SECRET || "super-secret-key";
export default class AuthService extends Service {
public constructor(broker: ServiceBroker) {
super(broker);
this.parseServiceSchema({
name: "auth",
actions: {
generateToken: {
params: {
userId: "string",
roles: { type: "array", items: "string", optional: true, default: [] },
},
handler(ctx): string {
return jwt.sign({ userId: ctx.params.userId, roles: ctx.params.roles }, JWT_SECRET, { expiresIn: "1h" });
},
},
verifyToken: {
params: {
token: "string",
},
handler(ctx): any {
try {
return jwt.verify(ctx.params.token, JWT_SECRET);
} catch (error: any) {
this.logger.warn("Invalid JWT token:", error.message);
throw new Error("Invalid Token");
}
},
},
},
});
}
}
// src/middlewares/auth.middleware.ts
import { Context, Middleware, ServiceBroker } from "moleculer";
export const AuthMiddleware = (broker: ServiceBroker): Middleware => {
return {
async call(next, action) {
// Check for custom 'auth' metadata on the action
if (action.auth === "required") {
const token = action.ctx.meta.token; // Assume token is passed in meta from API Gateway
if (!token) {
throw new Error("Authentication required.");
}
try {
const decoded = await broker.call("auth.verifyToken", { token });
action.ctx.meta.user = decoded; // Attach decoded user info to context
} catch (error) {
throw new Error("Invalid or expired token.");
}
}
return next(action);
},
};
};
The AuthMiddleware intercepts service calls, validates the JWT, and attaches user information to the context for subsequent authorization checks.
Real-Time WebSockets with moleculer-io
moleculer-io seamlessly integrates Socket.IO with Moleculer, enabling real-time communication across your microservices. We'll use it to broadcast chat messages.
// services/chat.service.ts
import { Service, ServiceBroker } from "moleculer";
import { IoService } from "moleculer-io"; // Import IoService from moleculer-io
export default class ChatService extends Service {
public constructor(broker: ServiceBroker) {
super(broker);
this.parseServiceSchema({
name: "chat",
mixins: [IoService], // Mixin IoService to enable WebSocket features
settings: {
io: {
namespaces: {
"/": {
// Middleware for Socket.IO connection authentication
middlewares: [
async (socket, next) => {
const token = socket.handshake.query.token as string;
if (!token) {
return next(new Error("Authentication required for WebSocket."));
}
try {
const decoded = await this.broker.call("auth.verifyToken", { token });
socket.data.user = decoded; // Attach user info to socket
next();
} catch (error) {
next(new Error("Invalid WebSocket token."));
}
},
],
events: {
// Handle incoming 'message' event from client
message: {
// Custom auth for Socket.IO event handler (can be extended)
handler(socket, payload: { text: string }) {
const { user } = socket.data;
this.logger.info(`User ${user.userId} sent message: ${payload.text}`);
// Broadcast message to all connected clients in this namespace
this.io.of("/").emit("message", {
userId: user.userId,
text: payload.text,
timestamp: Date.now(),
});
},
},
},
},
},
},
},
actions: {
// Example action to send a server-initiated message
sendMessageToAll: {
params: {
text: "string",
},
handler(ctx) {
this.io.of("/").emit("serverMessage", { text: ctx.params.text, timestamp: Date.now() });
return true;
},
},
},
});
}
}
The chat service uses IoService to manage Socket.IO connections and events, including an authentication middleware for WebSocket connections and an event handler for broadcasting messages.
Cloudflare Integration
Cloudflare provides a global network that enhances performance, security, and reliability. By proxying your domain through Cloudflare, it automatically handles DDoS protection, WAF, and SSL/TLS termination, including for WebSockets (wss://).
- DNS Configuration: Point your domain's
AorCNAMErecord to your Moleculer API Gateway's public IP/hostname, ensuring the "Proxy status" is "Proxied" (orange cloud). - WebSocket Proxying: Cloudflare automatically proxies WebSocket traffic (port 80/443) when HTTP proxying is enabled. No special configuration is usually needed for standard
wss://connections. - Security Rules: Leverage Cloudflare's Web Application Firewall (WAF) to add custom rules for protecting your API endpoints and mitigating common web vulnerabilities.
- Rate Limiting: Implement Cloudflare rate limiting rules to prevent abuse of your API and WebSocket connections.
Cloudflare sits in front of your Moleculer API Gateway (likely using moleculer-web with moleculer-io attached), handling all incoming requests and securing them before they reach your infrastructure.
Conclusion
By integrating Moleculer microservices with Cloudflare, secure authentication, and real-time WebSockets, you build a robust, scalable, and performant application. This architecture provides modularity, enhanced security, and a seamless real-time experience for users, all powered by TypeScript. Embrace these tools to deliver the next generation of web applications.