Skip to content

Commit d5ba448

Browse files
improving loading response of free tier backend
1 parent 7303049 commit d5ba448

File tree

9 files changed

+178
-13
lines changed

9 files changed

+178
-13
lines changed

apps/api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,19 @@
3939
"@nestjs/typeorm": "^10.0.2",
4040
"@nestjs/websockets": "^10.4.6",
4141
"@sendgrid/mail": "^8.1.3",
42+
"@types/compression": "^1.7.5",
4243
"bcrypt": "^5.1.1",
4344
"body-parser": "^1.20.3",
4445
"cache-manager": "^5.7.6",
4546
"class-transformer": "^0.5.1",
4647
"class-validator": "^0.14.0",
4748
"cloudinary": "^1.41.3",
49+
"compression": "^1.8.0",
4850
"dotenv": "^16.4.5",
4951
"firebase": "^11.5.0",
5052
"lodash": "^4.17.21",
5153
"multer-storage-cloudinary": "^4.0.0",
54+
"node-cache": "^5.1.2",
5255
"nodemailer": "^6.9.15",
5356
"passport": "^0.7.0",
5457
"passport-jwt": "^4.0.1",

apps/api/src/app.controller.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ export class AppController {
99
getHello(): string {
1010
return this.appService.getHello();
1111
}
12+
13+
@Get('keep-alive')
14+
keepAlive(): string {
15+
return 'OK';
16+
}
1217
}

apps/api/src/app.module.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import { AppService } from './app.service';
99
import { SkillsModule } from './skills/skills.module';
1010
import { CacheModule } from '@nestjs/cache-manager';
1111
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
12-
import { APP_GUARD } from '@nestjs/core';
12+
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
1313
import { SendGridModule } from './sendgrid/sendgrid.module';
1414
import { ChatGateway } from './chat/chat.gateway';
1515
import { ChatModule } from './chat/chat.module';
1616
import { RoomsModule } from './chat/rooms.module';
1717
import { FirebaseModule } from './firebase/firebase.module';
1818
import firebaseConfig from './config/firebase.config';
19+
import { HttpCacheInterceptor } from './common/interceptors/cache.interceptor';
1920

2021
@Module({
2122
imports: [
@@ -56,6 +57,10 @@ import firebaseConfig from './config/firebase.config';
5657
provide: APP_GUARD,
5758
useClass: ThrottlerGuard, // Apply throttling globally
5859
},
60+
{
61+
provide: APP_INTERCEPTOR,
62+
useClass: HttpCacheInterceptor,
63+
},
5964
],
6065
})
6166
export class AppModule {}

apps/api/src/chat/chat.service.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import { Equal, FindOptionsWhere, Repository } from 'typeorm';
88
import { User } from '../users/entities/user.entity';
99
import { ChatMessage } from './entities/chat-message.entity';
1010
import { Room } from './entities/room.entity';
11+
import * as NodeCache from 'node-cache';
1112

1213
@Injectable()
1314
export class ChatService {
15+
private cache: NodeCache;
16+
1417
constructor(
1518
@InjectRepository(ChatMessage)
1619
private chatRepository: Repository<ChatMessage>,
@@ -20,13 +23,23 @@ export class ChatService {
2023

2124
@InjectRepository(Room)
2225
private roomRepository: Repository<Room>
23-
) {}
26+
) {
27+
// Initialize cache with 5 minute TTL
28+
this.cache = new NodeCache({ stdTTL: 300 });
29+
}
2430

2531
async getUserById(id: string): Promise<User> {
32+
// Try to get from cache first
33+
const cachedUser = this.cache.get<User>(`user:${id}`);
34+
if (cachedUser) return cachedUser;
35+
2636
const user = await this.userRepository.findOne({ where: { id } });
2737
if (!user) {
2838
throw new NotFoundException(`User with ID ${id} not found`);
2939
}
40+
41+
// Store in cache
42+
this.cache.set(`user:${id}`, user);
3043
return user;
3144
}
3245

@@ -134,12 +147,20 @@ export class ChatService {
134147
}
135148

136149
async findRoomBetweenUsers(user1: User, user2: User): Promise<Room | null> {
150+
const cacheKey = `room:${user1.id}:${user2.id}`;
151+
const cachedRoom = this.cache.get<Room>(cacheKey);
152+
if (cachedRoom) return cachedRoom;
153+
137154
const room = await this.roomRepository.findOne({
138155
where: [
139156
{ employer: Equal(user1.id), freelancer: Equal(user2.id) },
140157
{ employer: Equal(user2.id), freelancer: Equal(user1.id) },
141158
],
142159
});
160+
161+
if (room) {
162+
this.cache.set(cacheKey, room);
163+
}
143164
return room || null;
144165
}
145166

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2+
import { Observable, of } from 'rxjs';
3+
import { tap } from 'rxjs/operators';
4+
import * as NodeCache from 'node-cache';
5+
6+
@Injectable()
7+
export class HttpCacheInterceptor implements NestInterceptor {
8+
private cache: NodeCache;
9+
10+
constructor() {
11+
// Cache with 5 minute TTL
12+
this.cache = new NodeCache({ stdTTL: 300 });
13+
}
14+
15+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
16+
const request = context.switchToHttp().getRequest();
17+
const cacheKey = `${request.method}-${request.url}`;
18+
19+
// Check if we have a cached response
20+
const cachedResponse = this.cache.get(cacheKey);
21+
if (cachedResponse) {
22+
return of(cachedResponse);
23+
}
24+
25+
// If not cached, call handler and cache response
26+
return next.handle().pipe(
27+
tap(response => {
28+
// Only cache GET requests
29+
if (request.method === 'GET') {
30+
this.cache.set(cacheKey, response);
31+
}
32+
}),
33+
);
34+
}
35+
}

apps/api/src/main.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,26 @@ import { AppModule } from './app.module';
33
import { ValidationPipe, Logger } from '@nestjs/common';
44
import * as bodyParser from 'body-parser';
55
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
6+
import * as compression from 'compression';
7+
import { Request, Response, NextFunction } from 'express';
68

79
async function bootstrap() {
810
const logger = new Logger('Bootstrap');
911

1012
const app = await NestFactory.create(AppModule);
1113

14+
// Enable compression
15+
app.use(compression());
16+
17+
// Add cache-control headers for static assets
18+
app.use((req: Request, res: Response, next: NextFunction) => {
19+
const staticExtensions = ['.js', '.css', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg'];
20+
if (staticExtensions.some(ext => req.path.endsWith(ext))) {
21+
res.set('Cache-Control', 'public, max-age=31536000'); // 1 year
22+
}
23+
next();
24+
});
25+
1226
// Set a global prefix for all routes
1327
app.setGlobalPrefix('api');
1428

apps/client/src/App.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
22
import Navbar from "./components/NavBar";
33
import Footer from "./components/Footer";
4-
import React, { Suspense } from "react";
4+
import React, { Suspense, useEffect } from "react";
55
import LoadingSpinner from "./helpers/LoadingSpinner";
66
import { UserProvider } from "./contexts/UserContext";
77
import { GradientProvider } from "./contexts/GradientContext";
@@ -11,10 +11,20 @@ import ResetPasswordPage from "./pages/ResetPasswordPage";
1111
import EmailVerificationPage from "./pages/EmailVerificationPage";
1212
import { SocketProvider } from "./contexts/SocketContext";
1313
import { Provider as JotaiProvider } from "jotai";
14+
import { startKeepAlive, stopKeepAlive } from "./services/keepAlive";
1415

1516
const HomePage = React.lazy(() => import("./pages/HomePage")); // Lazy load the HomePage
1617

1718
function App() {
19+
useEffect(() => {
20+
// Start the keep-alive ping when the app loads
21+
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:3000';
22+
startKeepAlive(apiUrl);
23+
24+
// Clean up when the app unmounts
25+
return () => stopKeepAlive();
26+
}, []);
27+
1828
return (
1929
<>
2030
<Toaster />
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
let keepAliveInterval: NodeJS.Timeout;
2+
3+
export const startKeepAlive = (apiUrl: string) => {
4+
// Clear any existing interval
5+
if (keepAliveInterval) {
6+
clearInterval(keepAliveInterval);
7+
}
8+
9+
// Ping the server every 4 minutes
10+
keepAliveInterval = setInterval(async () => {
11+
try {
12+
await fetch(`${apiUrl}/keep-alive`);
13+
} catch (error) {
14+
console.error('Keep-alive ping failed:', error);
15+
}
16+
}, 240000); // 4 minutes
17+
};
18+
19+
export const stopKeepAlive = () => {
20+
if (keepAliveInterval) {
21+
clearInterval(keepAliveInterval);
22+
}
23+
};

0 commit comments

Comments
 (0)