Skip to content

Commit fc7a5e7

Browse files
feat: singleton refactor (#8)
1 parent a87f302 commit fc7a5e7

File tree

12 files changed

+246
-231
lines changed

12 files changed

+246
-231
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `Testing Lab`
22

3-
This repository contains a small backend server built with **Bun**, **Hono**, **Redis**, and **TypeScript**, designed as a playground for exploring different testing strategies.
3+
This repository contains a small backend server built with **Bun**, **Hono**, **Redis**, and **TypeScript**, designed as a playground for exploring different testing strategies.
44

55
The goal of this project is to provide clear, practical examples of **unit tests**, **integration tests**, and **end-to-end (E2E) tests** while experimenting with modern backend tools.
66

@@ -25,9 +25,11 @@ This project is intended as a **testing playground**, where you can:
2525
```sh
2626
bun run dev
2727
```
28+
2829
The server will automatically reload when files change.
2930

3031
## `🧪 Running tests`
32+
3133
```shell
3234
bun test
3335
```
@@ -39,4 +41,4 @@ Tests will be progressively added across all categories (unit, integration, and
3941
### `📝 Notes`
4042

4143
This project is intentionally minimal and modular to make testing patterns easy to understand and extend.
42-
More test cases, utilities, and scenarios will be added over time.
44+
More test cases, utilities, and scenarios will be added over time.

index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1-
import { startServer } from "./src/server";
1+
import { HttpServer } from "./src/http/server.ts";
2+
import { RedisService } from "./src/services/redisService.ts";
23

3-
await startServer();
4+
async function main() {
5+
await RedisService.start();
6+
await HttpServer.start();
7+
8+
const gracefulShutdown = async () => {
9+
await HttpServer.stop();
10+
await RedisService.stop();
11+
process.exit(0);
12+
};
13+
14+
process.on("SIGINT", gracefulShutdown);
15+
process.on("SIGTERM", gracefulShutdown);
16+
}
17+
18+
await main();

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"dev": "bun --hot run index.ts",
88
"start": "bun index.ts",
99
"test": "bun test",
10-
"build": "bun build index.ts --outdir dist --minify --target bun"
10+
"build": "bun build index.ts --outdir dist --minify --target bun",
11+
"format": "npx prettier --write ."
1112
},
1213
"devDependencies": {
1314
"@types/bun": "latest",

src/app.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {Hono} from "hono";
2-
import {messageRoutes} from "./routes/messages.ts";
1+
import { Hono } from "hono";
2+
import { messageRoutes } from "./routes/messages.ts";
33

4-
const app = new Hono()
4+
const app = new Hono();
55

6-
app.route('/messages', messageRoutes)
6+
app.route("/messages", messageRoutes);
77

8-
export default app;
8+
export default app;

src/http/server.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { serve } from "bun";
2+
import app from "../app.ts";
3+
4+
export class HttpServer {
5+
private static server: ReturnType<typeof serve> | null = null;
6+
private static port = 3000;
7+
8+
static async start() {
9+
this.server = serve({
10+
port: this.port,
11+
fetch: app.fetch,
12+
});
13+
console.log(
14+
`\x1b[32m 📡 Server up and running on port :${this.port} \x1b[0m`,
15+
);
16+
}
17+
18+
static async stop() {
19+
this.server?.stop();
20+
console.log("\x1b[31m 🔴 HTTP server stopped \x1b[0m");
21+
}
22+
}

src/routes/messages.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
import { Hono } from 'hono'
2-
import { redis } from '../services/redisService'
1+
import { Hono } from "hono";
2+
import { RedisService } from "../services/redisService.ts";
33

4-
export const messageRoutes = new Hono()
4+
export const messageRoutes = new Hono();
55

6-
messageRoutes.post('/', async (c) => {
7-
try {
8-
const body = await c.req.json().catch(() => null)
6+
messageRoutes.post("/", async (c) => {
7+
try {
8+
const body = await c.req.json().catch(() => null);
99

10-
if (!body?.text || typeof body.text !== 'string') {
11-
console.warn('[HTTP] Invalid payload received')
12-
return c.json({ error: 'Invalid body: expected { text: string }' }, 400)
13-
}
10+
if (!body?.text || typeof body.text !== "string") {
11+
console.warn("[HTTP] Invalid payload received");
12+
return c.json({ error: "Invalid body: expected { text: string }" }, 400);
13+
}
1414

15-
await redis.lpush('messages', body.text)
15+
await RedisService.lpush("messages", body.text);
1616

17-
console.log('[HTTP] Message stored successfully')
18-
return c.json({ ok: true })
19-
} catch (err) {
20-
console.error('[HTTP] Error in POST /messages:', err)
21-
return c.json({ error: 'Internal server error' }, 500)
22-
}
23-
})
17+
console.log("[HTTP] Message stored successfully");
18+
return c.json({ ok: true });
19+
} catch (err) {
20+
console.error("[HTTP] Error in POST /messages:", err);
21+
return c.json({ error: "Internal server error" }, 500);
22+
}
23+
});
2424

25-
messageRoutes.get('/', async (c) => {
26-
try {
27-
const messages = await redis.lrange('messages', 0, 9)
28-
return c.json({ count: messages.length, messages })
29-
} catch (err) {
30-
console.error('[HTTP] Error in GET /messages:', err)
31-
return c.json({ error: 'Internal server error' }, 500)
32-
}
33-
})
25+
messageRoutes.get("/", async (c) => {
26+
try {
27+
const messages = await RedisService.lrange("messages", 0, 9);
28+
return c.json({ count: messages.length, messages });
29+
} catch (err) {
30+
console.error("[HTTP] Error in GET /messages:", err);
31+
return c.json({ error: "Internal server error" }, 500);
32+
}
33+
});

src/server.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/services/redisService.ts

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,34 @@
1-
import { createClient } from 'redis'
2-
import type { RedisLike } from "../types/redisTypes.ts";
1+
import { createClient } from "redis";
32

43
export class RedisService {
5-
constructor(private client: RedisLike) {}
6-
7-
async lpush(key: string, value: string) {
8-
console.log(`[Redis] LPUSH -> key="${key}", value="${value}"`);
9-
try {
10-
const result = await this.client.lPush(key, value);
11-
console.log(`[Redis] LPUSH result ->`, result);
12-
return result;
13-
} catch (err) {
14-
console.error(`[Redis] LPUSH error:`, err);
15-
throw err;
16-
}
4+
private static client: ReturnType<typeof createClient> | null = null;
5+
6+
static async start() {
7+
const c = createClient({ url: "redis://localhost:6379" });
8+
await c.connect();
9+
this.client = c;
10+
console.log("\x1b[32m 💾 Redis connected successfully \x1b[0m");
11+
}
12+
13+
static async stop() {
14+
if (this.client?.isOpen) {
15+
await this.client.quit();
16+
console.log("\x1b[31m 🔴 Redis stopped \x1b[0m");
1717
}
18+
}
1819

19-
async lrange(key: string, start: number, stop: number) {
20-
console.log(`[Redis] LRANGE -> key="${key}", start=${start}, stop=${stop}`);
21-
try {
22-
const result = await this.client.lRange(key, start, stop);
23-
console.log(`[Redis] LRANGE result ->`, result);
24-
return result;
25-
} catch (err) {
26-
console.error(`[Redis] LRANGE error:`, err);
27-
throw err;
28-
}
20+
static op() {
21+
if (!this.client) {
22+
throw new Error("RedisService not initialized");
2923
}
30-
}
31-
32-
export const redisClient = createClient({
33-
url: 'redis://localhost:6379' // TODO: Podría pasarse esto a un .env
34-
})
24+
return this.client;
25+
}
3526

36-
redisClient.on('error', (err) => {
37-
console.error('[Redis] Connection error:', err);
38-
});
27+
static lpush(key: string, value: string) {
28+
return this.client!.lPush(key, value);
29+
}
3930

40-
await redisClient.connect();
41-
42-
console.log('[Redis] Client connected successfully.');
43-
44-
export const redis = new RedisService(redisClient);
31+
static lrange(key: string, start: number, stop: number) {
32+
return this.client!.lRange(key, start, stop);
33+
}
34+
}

src/types/redisTypes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export interface RedisLike {
2-
lPush(key: string, value: string): Promise<number>
3-
lRange(key: string, start: number, stop: number): Promise<string[]>
2+
lPush(key: string, value: string): Promise<number>;
3+
lRange(key: string, start: number, stop: number): Promise<string[]>;
44
}

test/example.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {describe, test, expect} from "bun:test";
1+
import { describe, test, expect } from "bun:test";
22

33
describe("Example test case", () => {
4-
test("Simple sum", async () => {
5-
const sum = 2 + 2;
6-
expect(sum).toBe(4);
7-
})
8-
})
4+
test("Simple sum", async () => {
5+
const sum = 2 + 2;
6+
expect(sum).toBe(4);
7+
});
8+
});

0 commit comments

Comments
 (0)