Building Microservices with ScoutQuest
Learn how to build a complete microservices architecture using ScoutQuest for service discovery. This tutorial will guide you through creating, registering, and connecting multiple services.
Overview
In this tutorial, you'll build a complete microservices system consisting of:
- User Service - Manages user data and authentication
- Product Service - Handles product catalog
- Order Service - Processes orders and communicates with other services
- API Gateway - Routes requests to appropriate services
Prerequisites
- Node.js 18+ or Rust 1.70+
- ScoutQuest server running (see Installation Guide)
- Basic understanding of microservices concepts
Step 1: User Service
Let's start by creating a simple user service using Express.js:
user-service/index.js
const express = require('express');
const { ScoutQuestClient } = require('scoutquest-js');
const app = express();
const port = process.env.PORT || 3001;
// Initialize ScoutQuest client
const scout = new ScoutQuestClient(process.env.SCOUT_URL || 'http://localhost:8080');
app.use(express.json());
// Sample user data
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', service: 'user-service' });
});
// User endpoints
app.get('/users', (req, res) => {
res.json(users);
});
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.post('/users', (req, res) => {
const newUser = {
id: users.length + 1,
name: req.body.name,
email: req.body.email
};
users.push(newUser);
res.status(201).json(newUser);
});
app.listen(port, async () => {
console.log(`User service listening on port ${port}`);
try {
// Register with ScoutQuest
await scout.registerService('user-service', 'localhost', port, {
tags: ['api', 'users', 'v1'],
metadata: {
version: '1.0.0',
description: 'User management service'
},
healthCheck: {
path: '/health',
interval: 30
}
});
console.log('✅ Registered with ScoutQuest');
} catch (error) {
console.error('❌ Failed to register with ScoutQuest:', error);
}
});
Step 2: Product Service
Create a product service that manages the product catalog:
product-service/index.js
const express = require('express');
const { ScoutQuestClient } = require('scoutquest-js');
const app = express();
const port = process.env.PORT || 3002;
const scout = new ScoutQuestClient(process.env.SCOUT_URL || 'http://localhost:8080');
app.use(express.json());
// Sample product data
const products = [
{ id: 1, name: 'Laptop', price: 999.99, stock: 10 },
{ id: 2, name: 'Phone', price: 699.99, stock: 25 },
{ id: 3, name: 'Tablet', price: 399.99, stock: 15 }
];
app.get('/health', (req, res) => {
res.json({ status: 'healthy', service: 'product-service' });
});
app.get('/products', (req, res) => {
res.json(products);
});
app.get('/products/:id', (req, res) => {
const product = products.find(p => p.id === parseInt(req.params.id));
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
});
app.put('/products/:id/stock', (req, res) => {
const product = products.find(p => p.id === parseInt(req.params.id));
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
product.stock = req.body.stock;
res.json(product);
});
app.listen(port, async () => {
console.log(`Product service listening on port ${port}`);
try {
await scout.registerService('product-service', 'localhost', port, {
tags: ['api', 'products', 'v1'],
metadata: {
version: '1.0.0',
description: 'Product catalog service'
},
healthCheck: {
path: '/health',
interval: 30
}
});
console.log('✅ Registered with ScoutQuest');
} catch (error) {
console.error('❌ Failed to register with ScoutQuest:', error);
}
});
Step 3: Order Service
Create an order service that communicates with other services:
order-service/index.js
const express = require('express');
const { ScoutQuestClient } = require('scoutquest-js');
const app = express();
const port = process.env.PORT || 3003;
const scout = new ScoutQuestClient(process.env.SCOUT_URL || 'http://localhost:8080');
app.use(express.json());
let orders = [];
let orderIdCounter = 1;
app.get('/health', (req, res) => {
res.json({ status: 'healthy', service: 'order-service' });
});
app.get('/orders', (req, res) => {
res.json(orders);
});
app.post('/orders', async (req, res) => {
try {
const { userId, productId, quantity } = req.body;
// Verify user exists
const user = await scout.get('user-service', `/users/${userId}`);
if (!user) {
return res.status(400).json({ error: 'Invalid user' });
}
// Verify product exists and has stock
const product = await scout.get('product-service', `/products/${productId}`);
if (!product) {
return res.status(400).json({ error: 'Invalid product' });
}
if (product.stock < quantity) {
return res.status(400).json({ error: 'Insufficient stock' });
}
// Update product stock
await scout.put('product-service', `/products/${productId}/stock`, {
stock: product.stock - quantity
});
// Create order
const order = {
id: orderIdCounter++,
userId,
productId,
quantity,
total: product.price * quantity,
status: 'confirmed',
createdAt: new Date().toISOString()
};
orders.push(order);
res.status(201).json(order);
} catch (error) {
console.error('Order creation failed:', error);
res.status(500).json({ error: 'Failed to create order' });
}
});
app.listen(port, async () => {
console.log(`Order service listening on port ${port}`);
try {
await scout.registerService('order-service', 'localhost', port, {
tags: ['api', 'orders', 'v1'],
metadata: {
version: '1.0.0',
description: 'Order processing service'
},
healthCheck: {
path: '/health',
interval: 30
}
});
console.log('✅ Registered with ScoutQuest');
} catch (error) {
console.error('❌ Failed to register with ScoutQuest:', error);
}
});
Step 4: API Gateway
Create an API gateway that routes requests to the appropriate services:
api-gateway/index.js
const express = require('express');
const { ScoutQuestClient } = require('scoutquest-js');
const app = express();
const port = process.env.PORT || 3000;
const scout = new ScoutQuestClient(process.env.SCOUT_URL || 'http://localhost:8080');
app.use(express.json());
// Middleware for service discovery and proxying
const proxyToService = (serviceName, pathPrefix = '') => {
return async (req, res, next) => {
try {
const method = req.method.toLowerCase();
const path = req.path.replace(pathPrefix, '');
let response;
switch (method) {
case 'get':
response = await scout.get(serviceName, path);
break;
case 'post':
response = await scout.post(serviceName, path, req.body);
break;
case 'put':
response = await scout.put(serviceName, path, req.body);
break;
case 'delete':
response = await scout.delete(serviceName, path);
break;
default:
return res.status(405).json({ error: 'Method not allowed' });
}
res.json(response);
} catch (error) {
console.error(`Proxy error for ${serviceName}:`, error);
res.status(error.response?.status || 500).json({
error: error.message || 'Service unavailable'
});
}
};
};
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy', service: 'api-gateway' });
});
// Route to services
app.use('/api/users*', proxyToService('user-service', '/api'));
app.use('/api/products*', proxyToService('product-service', '/api'));
app.use('/api/orders*', proxyToService('order-service', '/api'));
// Service discovery endpoint
app.get('/services', async (req, res) => {
try {
const services = await scout.listServices();
res.json(services);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch services' });
}
});
app.listen(port, async () => {
console.log(`API Gateway listening on port ${port}`);
try {
await scout.registerService('api-gateway', 'localhost', port, {
tags: ['gateway', 'api', 'v1'],
metadata: {
version: '1.0.0',
description: 'API Gateway service'
},
healthCheck: {
path: '/health',
interval: 30
}
});
console.log('✅ Registered with ScoutQuest');
} catch (error) {
console.error('❌ Failed to register with ScoutQuest:', error);
}
});
Step 5: Running the System
Create a script to start all services:
package.json
{
"name": "microservices-tutorial",
"version": "1.0.0",
"scripts": {
"start:user": "cd user-service && node index.js",
"start:product": "cd product-service && node index.js",
"start:order": "cd order-service && node index.js",
"start:gateway": "cd api-gateway && node index.js",
"start:all": "concurrently \"npm run start:user\" \"npm run start:product\" \"npm run start:order\" \"npm run start:gateway\""
},
"dependencies": {
"express": "^4.18.0",
"scoutquest-js": "^1.0.0"
},
"devDependencies": {
"concurrently": "^7.6.0"
}
}
Start all services:
# Install dependencies
npm install
# Start all services
npm run start:all
Step 6: Testing the System
Test your microservices system with these API calls:
Create a user
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice Johnson", "email": "alice@example.com"}'
Get products
curl http://localhost:3000/api/products
Create an order
curl -X POST http://localhost:3000/api/orders \
-H "Content-Type: application/json" \
-d '{"userId": 1, "productId": 1, "quantity": 2}'
Check service registry
curl http://localhost:3000/services
Best Practices
- Health Checks: Always implement health check endpoints
- Error Handling: Handle service unavailability gracefully
- Circuit Breakers: Implement circuit breakers for resilience
- Monitoring: Add logging and metrics to all services
- Configuration: Use environment variables for service configuration
Next Steps
- Add authentication and authorization
- Implement distributed tracing
- Add message queuing for async communication
- Deploy with Docker and Kubernetes
- Set up monitoring and alerting