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:
- Easy to configure and implement
- Supports various options for path rewriting, request modification, and error handling
- Works well with Express.js
- Allows integration of custom middleware
How to Add an Endpoint
To add a new endpoint to the API Gateway:
- Open the
src/app.tsfile. - Add a new proxy configuration using the
createProxyMiddlewarefunction:
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:
-
Flexibility: The Node.js API Gateway allows for greater programmatic control and implementation of custom logic in JavaScript/TypeScript.
-
JWT Validation: Our API Gateway performs JWT validation in the Node.js code, whereas Nginx would require additional modules or external services for this.
-
Dynamic Configuration: The Node.js gateway can be easily updated and reconfigured without a restart, while Nginx generally requires a reload for configuration changes.
-
Performance: Nginx is generally more efficient for simple proxying tasks, but our Node.js gateway offers more flexibility for complex operations.
-
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:
- The
validateJwtmiddleware insrc/app.tsintercepts all requests. - It checks for the presence of a JWT in the
Authorizationheader. - If a token is present, it is verified using the
jsonwebtokenlibrary. - 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.