Many people may still think of alova as a lightweight request strategy library, which is certainly the core feature of alova 2. For example, consider the following code:
const { loading, data, error } = useRequest(() => alovaInstance.Get('/xxx'))
This is a typical example of alova being used on the client side. However, alova has now been updated to version 3. Of course, these client strategies remain intact, but it's no longer limited to the client side—it can now handle server-side scenarios with ease.
Alova 3 provides server-side request strategies (server hooks) and server-side storage adapters like Redis and file storage, allowing us to conveniently implement full-chain requests and forwarding on the server side.
Let's first look at the complete flow of a request:
Client (Browser/App)
→ Node.js BFF Layer (data transformation, etc.)
→ API Gateway (authentication, rate limiting, routing, etc.)
→ Backend Microservices
The server hooks and distributed multi-level caching provided by alova allow us to easily implement request handling at all of the above levels.
Forwarding Client Requests in the BFF Layer
In the BFF layer, we often need to forward client requests to backend microservices. You can use async_hooks to access the context of each request and add it to the request in alova's beforeRequest, enabling the forwarding of user-related data.
import { createAlova } from 'alova';
import adapterFetch from '@alova/fetch';
import express from 'express';
import { AsyncLocalStorage } from 'node:async_hooks';
// Create async local storage instance
const asyncLocalStorage = new AsyncLocalStorage();
const alovaInstance = createAlova({
requestAdapter: adapterFetch(),
beforeRequest(method) {
// Get request headers from async context and pass to downstream
const context = asyncLocalStorage.getStore();
if (context && context.headers) {
method.config.headers = {
...method.config.headers,
...context.headers
};
}
},
responded: {
onSuccess(response) {
// Data transformation processing
return {
data: response.data,
timestamp: Date.now(),
transformed: true
};
},
onError(error) {
console.error('Request failed:', error);
throw error;
}
}
});
const app = express();
// Set once in middleware, automatically passed throughout
app.use((req, res, next) => {
const context = {
userId: req.headers['x-user-id'],
token: req.headers['authorization']
};
asyncLocalStorage.run(context, next);
});
// Business code focuses on business logic
app.get('/api/user-profile', async (req, res) => {
// No need to manually pass context anymore!
const [userInfo, orders] = await Promise.all([
alovaInstance.Get('http://gateway.com/user/profile'),
alovaInstance.Get('http://gateway.com/order/recent')
]);
res.json({ user: userInfo.data, orders: orders.data });
});
Use Cases in API Gateway
In gateways, authentication, request rate limiting, and request distribution are often needed. Alova 3's Redis storage adapter and rateLimiter can effectively implement distributed authentication services and request rate limiting.
Authentication Can Be Done Like This
If authentication tokens have an expiration time, you can configure a Redis storage adapter in the gateway to store tokens in Redis for reuse. For single-machine cluster services, you can also use the @alova/storage-file file storage adapter.
import { createAlova } from 'alova';
import RedisStorageAdapter from '@alova/storage-redis';
import adapterFetch from '@alova/fetch';
import express from 'express';
const redisAdapter = new RedisStorageAdapter({
host: 'localhost',
port: '6379',
username: 'default',
password: 'my-top-secret',
db: 0
});
const gatewayAlova = createAlova({
requestAdapter: adapterFetch(),
async beforeRequest(method) {
const newToken = await authRequest(method.config.headers['Authorization'], method.config.headers['UserId'])
method.config.headers['Authorization'] = `Bearer ${newToken}`;
}
// Set L2 storage adapter
l2Cache: redisAdapter,
// ...
});
const authRequest = (token, userId) => gatewayAlova.Post('http://auth.com/auth/token', null, {
// Set 3-hour cache, will be saved in Redis, subsequent requests with same parameters will hit cache
cacheFor: {
mode: 'restore',
expire: 3 * 3600 * 1000
},
headers: {
'x-user-id': userId,
'Authorization': `Bearer ${token}`
}
});
const app = express();
// Implement app to receive all requests and forward to alova
// Register routes for all HTTP methods
const methods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'];
methods.forEach(method => {
app[method]('*', async (req, res) => {
const { method, originalUrl, headers, body, query } = req;
// Use alova to send request
const response = await gatewayAlova.Request({
method: method.toLowerCase(),
url: originalUrl,
params: query,
data: body,
headers
});
// Forward response headers
for (const [key, value] of response.headers.entries()) {
res.setHeader(key, value);
}
// Send response data
res.status(response.status).send(await response.json());
});
});
app.listen(3000, () => {
console.log('Gateway server started on port 3000');
});
Of course, if you need to re-authenticate for every request, you can also remove cacheFor in authRequest to disable caching.
Rate Limiting Strategy
Alova's rateLimiter can implement distributed rate limiting strategies, internally using node-rate-limiter-flexible. Let's refactor the implementation:
import { createRateLimiter } from 'alova/server';
const rateLimit = createRateLimiter({
/**
* Time for points reset, in ms
* @default 4000
*/
duration: 60 * 1000,
/**
* Maximum consumable quantity within duration
* @default 4
*/
points: 4,
/**
* Namespace, prevents conflicts when multiple rateLimits use the same storage
*/
keyPrefix: 'user-rate-limit',
/**
* Lock duration in ms, indicates that when rate limit is reached, it will extend [blockDuration]ms, e.g., 5 wrong passwords in 1 hour locks for 24 hours, this 24 hours is this parameter
*/
blockDuration: 24 * 60 * 60 * 1000
});
const methods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'];
methods.forEach(method => {
app[method]('*', async (req, res) => {
const { method, originalUrl, headers, body, query } = req;
// Use rateLimit wrapper here, it will use l2Cache storage adapter by default to store control parameters, in this example it will use Redis storage adapter.
const method = gatewayAlova.Request({
method: method.toLowerCase(),
url: originalUrl,
params: query,
data: body,
headers
});
const response = await rateLimit(method, {
key: req.ip // Use IP as tracking key to prevent frequent requests from the same IP
});
// ...
});
});
Third-Party Service Integration: Automatic Token Maintenance
Integrating with external APIs requires access_token management, and many third-party access_tokens have usage limits. Here we can use alova 3 + Redis storage adapter to implement distributed automatic lifecycle maintenance of access_tokens, where Redis is used for access_token caching, and atom hook is used for atomic operations of distributed token updates.
import { createAlova, queryCache } from 'alova';
import RedisStorageAdapter from '@alova/storage-redis';
import adapterFetch from '@alova/fetch';
import { atomize } from 'alova/server';
const redisAdapter = new RedisStorageAdapter({
host: 'localhost',
port: '6379',
username: 'default',
password: 'my-top-secret',
db: 0
});
const thirdPartyAlova = createAlova({
requestAdapter: adapterFetch(),
async beforeRequest(method) {
// Check if it's a third-party API, if so, get the token
if (method.meta?.isThirdPartyApi) {
// Get token in an atomic way to prevent multiple processes from getting token simultaneously
const accessTokenGetMethod = getAccessToken();
let accessToken = await queryCache(accessTokenGetMethod);
if (!accessToken) {
// Will be cached after successful retrieval
accessToken = await atomize(accessTokenGetMethod);
}
method.config.params.access_token = accessToken;
}
},
l2Cache: redisAdapter,
});
const getAccessToken = () => thirdPartyAlova.Get('http://third-party.com/token', {
params: {
grant_type: 'client_credentials',
client_id: process.env.THIRD_PARTY_CLIENT_ID,
client_secret: process.env.THIRD_PARTY_CLIENT_SECRET
},
cacheFor: {
mode: 'restore',
expire: 1 * 3600 * 1000 // Two hours cache time
}
});
const getThirdPartyUserInfo = userId => thirdPartyAlova.Get('http://third-party.com/user/info', {
params: {
userId
},
meta: {
isThirdPartyApi: true
}
});
Final Thoughts
In addition to these, alova also provides distributed verification code sending and validation, request retry, and other server hooks. If you want to learn more, you can refer to Server-Side Request Strategies.
If you think alova is good, we sincerely hope you can try it out and give us a free GitHub star.
Visit the alovajs official website for more detailed information: alovajs Official Website.
If you're interested, you can join our community to get the latest updates first and communicate directly with the development team, sharing your ideas and suggestions.
If you have any questions, you can join the above groups for consultation, or post in GitHub repository Discussions. If you encounter problems, please submit them in GitHub issues, and we will resolve them as quickly as possible.
Top comments (0)