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