Skip to content

Sprint 4 - Response Visualization

In this module, we will only create endpoints that allow collaborators and PDMs to view the feedback received. To do this, we will remap the entities that already exist in other modules to make the correct query and display it to the user.

NodeJS API Gateway

Key Features

  • Routing requests to the appropriate microservices
  • JWT validation
  • Request/response transformation
  • Load balancing (when configured)

Library Used for Proxying Requests

We use the http-proxy-middleware library for proxying requests. This library provides a flexible way to forward HTTP requests to other servers.

Key benefits of http-proxy-middleware:

  1. Easy to configure and implement
  2. Supports various options for path rewriting, request modification, and error handling
  3. Works well with Express.js
  4. Allows integration of custom middleware

How to Add an Endpoint

To add a new endpoint to the API Gateway:

  1. Open the src/app.ts file.
  2. Add a new proxy configuration using the createProxyMiddleware function:
app.use('/new-service', createProxyMiddleware({
  target: 'http://new-service:8080',
  changeOrigin: true,
  pathRewrite: {
    '^/new-service': '',
  },
}));

Replace '/new-service' with the desired path and 'http://new-service:8080' with the URL of the actual service.

Note

pathRewrite is a feature of the http-proxy-middleware library that allows you to modify the URL path of a request before it is forwarded to the target server. This is useful when you want to hide or change parts of the original path, ensuring that the target server receives an appropriate URL format. For example, by using pathRewrite to replace a specific path with an empty string, you can make the request cleaner and more compatible with the internal structure of the microservice, facilitating routing and integration between different services in the microservices architecture.

Key Differences from Nginx

Although both our Node.js API Gateway and Nginx (configured in gateway/nginx.conf) can act as reverse proxies, there are some key differences:

  1. Flexibility: The Node.js API Gateway allows for greater programmatic control and implementation of custom logic in JavaScript/TypeScript.

  2. JWT Validation: Our API Gateway performs JWT validation in the Node.js code, whereas Nginx would require additional modules or external services for this.

  3. Dynamic Configuration: The Node.js gateway can be easily updated and reconfigured without a restart, while Nginx generally requires a reload for configuration changes.

  4. Performance: Nginx is generally more efficient for simple proxying tasks, but our Node.js gateway offers more flexibility for complex operations.

  5. Load Balancing: Although both can perform load balancing, Nginx has more built-in features for it, while our Node.js gateway would require additional implementation.

JWT Validation

JWT validation is performed in the API Gateway to ensure that only authenticated requests reach the backend services. Here's how it works:

  1. The validateJwt middleware in src/app.ts intercepts all requests.
  2. It checks for the presence of a JWT in the Authorization header.
  3. If a token is present, it is verified using the jsonwebtoken library.
  4. If the token is valid, the request proceeds; otherwise, it is rejected with a 401 Unauthorized response.

Changing the Filter in the Java Backend

From the moment Node starts validating the JWT token, it will stop sending it to the backend and will instead send only the user payload as part of the headers. See below:

app.use((req: Request, res: Response, next: NextFunction) => {
    const token = req.headers['authorization']?.split(' ')[1]; // Get the token from the header

    jwt.verify(token, SECRET_KEY, {issuer: ISSUER}, (err: any, decoded: any) => {
        // Add the token payload to the 'x-user' header
        req.headers['x-user'] = JSON.stringify(decoded['user']); // Convert the payload to a string

        next(); // Valid token, continue to the next function/middleware
    });
});

const proxyEvents: OnProxyEvent = {
    proxyReq: (proxyReq, req, res) => {
        proxyReq.setHeader("x-user", req.headers['x-user'] || "")
    }
}

app.use('/users', createProxyMiddleware({
    // other options
    on: proxyEvents, // passes the event consumer that will forward the user payload to the backend
    // other options
}));

In Java, we must then change the way we validate the logged-in user. For this, we already have the correct filter implemented in the security lib. Just go to the microservices and change the annotation from @EnableJwtSecurity to @EnableUserClaimsSecurity. The rest of the security remains the same.

Feedback Response Visualization Service in Python

Overview

This service acts as a Backend for Frontend (BFF) for visualizing feedback responses. It uses FastAPI as the web framework and Peewee as the ORM.

Backend for Frontend (BFF) Concept

A Backend for Frontend (BFF) is an architectural pattern where a dedicated backend service is created to serve a specific frontend application or client. In this case, our BFF is tailored for visualizing feedback responses.

Benefits of BFF: - Optimized for specific frontend needs - Improved performance by aggregating multiple backend calls - Easier to maintain and evolve along with the frontend

FastAPI and Peewee Integration

Configuring FastAPI

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Welcome to the Feedback Response Visualization BFF"}

Peewee Models

Create your Peewee models in models.py:

from peewee import *

database = PostgresqlDatabase('your_database', user='your_user', password='your_password', host='your_host')

class BaseModel(Model):
    class Meta:
        database = database

class FeedbackResponse(BaseModel):
    id = AutoField()
    content = TextField()
    created_at = DateTimeField()
    # Add other fields as needed

FastAPI Endpoints with Peewee

from fastapi import FastAPI, Depends, HTTPException
from .models import FeedbackResponse

app = FastAPI()

@app.get("/feedback-responses/")
async def get_feedback_responses():
    responses = FeedbackResponse.select()
    return [{"id": r.id, "content": r.content, "created_at": r.created_at} for r in responses]

User Authentication and Authorization

It will be necessary to capture the user payload passed by the gateway in the x-user header. This becomes simple in FastAPI by using the Header object and the Depends object.

To capture user data from the x-user header and perform authorization:

from fastapi import Header, HTTPException

async def get_current_user(x_user: str = Header(None)):
    if not x_user:
        raise HTTPException(status_code=401, detail="X-User header is missing")
    # Parse the x-user header and extract user information
    # Implement your logic to validate user access
    return {"user_id": "extracted_user_id"}

@app.get("/protected-route/")
async def protected_route(current_user: dict = Depends(get_current_user)):
    # This route is now protected and accessible only to authenticated users
    return {"message": f"Hello, user {current_user['user_id']}"}

Poetry for Dependency Management

Poetry is a tool for dependency management and packaging in Python. It allows you to declare the libraries your project depends on, and it will manage (install/update) them for you.

Reference guide

  1. NodeJS Technical Refinement
  2. Nginx Technical Refinement
  3. FastApi
  4. Api Gateway
  5. NodeJS Express

References