Skip to content

Commit d78eb51

Browse files
author
Braden Wong
committed
docs: show multiple service patterns with tabs
Present all four approaches for grouping service functions (plain object, module exports, namespace, abstract class) using tabs, letting developers choose based on their background and preferences. Add tip callouts in other locations pointing to this central reference. Changes: - Add Tab component to best-practice.md with 4 pattern options - Add "Module Exports" pattern (import * as) for ES module-native approach - Reorder patterns: Plain Object → Module Exports → Namespace → Abstract Class - Add tip callout after Controller "Do" example linking to Service patterns - Add tip callout in key-concept.md linking to Service patterns - Update main service.ts example to use plain object (simplest approach)
1 parent 5e5da8b commit d78eb51

File tree

2 files changed

+107
-9
lines changed

2 files changed

+107
-9
lines changed

docs/essential/best-practice.md

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ head:
1414
content: Elysia is a pattern agnostic framework, we leave the decision up to you and your team for coding patterns to use. However, we found that there are several who are using MVC pattern (Model-View-Controller) on Elysia, and found it's hard to decouple and handle types. This page is a guide to use Elysia with MVC pattern.
1515
---
1616

17+
<script setup>
18+
import Tab from '../components/fern/tab.vue'
19+
</script>
20+
1721
# Best Practice
1822

1923
Elysia is a pattern-agnostic framework, leaving the decision of which coding patterns to use up to you and your team.
@@ -85,10 +89,9 @@ import { status } from 'elysia'
8589

8690
import type { AuthModel } from './model'
8791

88-
// If the class doesn't need to store a property,
89-
// you may use `abstract class` to avoid class allocation
90-
export abstract class Auth {
91-
static async signIn({ username, password }: AuthModel.signInBody) {
92+
// Group related functions - see Service section for alternative patterns
93+
export const Auth = {
94+
async signIn({ username, password }: AuthModel.signInBody) {
9295
const user = await sql`
9396
SELECT password
9497
FROM users
@@ -211,6 +214,10 @@ new Elysia()
211214
.get('/', ({ stuff }) => Controller.doStuff(stuff))
212215
```
213216

217+
::: tip
218+
This example uses an abstract class, but you can also use plain objects, module exports, or namespaces. See [Service patterns](#1-abstract-away-non-request-dependent-service) for alternatives.
219+
:::
220+
214221
Tying the controller to Elysia Context may lead to:
215222
1. Loss of type integrity
216223
2. Make it harder to test and reuse
@@ -280,16 +287,99 @@ There are 2 types of service in Elysia:
280287

281288
We recommend abstracting a service class/function away from Elysia.
282289

283-
If the service or function isn't tied to an HTTP request or doesn't access a `Context`, it's recommended to implement it as a static class or function.
290+
If the service or function isn't tied to an HTTP request or doesn't access a `Context`, you can group related functions using several patterns depending on your preference:
291+
292+
<Tab
293+
id="service-pattern"
294+
:names="['Plain Object', 'Module Exports', 'Namespace', 'Abstract Class']"
295+
:tabs="['object', 'module', 'namespace', 'class']"
296+
>
297+
298+
<template v-slot:object>
299+
300+
The simplest approach is using a plain object. This is idiomatic JavaScript and easy to understand:
301+
302+
```typescript
303+
import { Elysia, t } from 'elysia'
304+
305+
const Service = {
306+
fibo(number: number): number {
307+
if (number < 2) return number
308+
return Service.fibo(number - 1) + Service.fibo(number - 2)
309+
}
310+
}
311+
312+
new Elysia()
313+
.get('/fibo', ({ body }) => {
314+
return Service.fibo(body)
315+
}, {
316+
body: t.Numeric()
317+
})
318+
```
319+
320+
</template>
321+
322+
<template v-slot:module>
323+
324+
Export functions directly from a module and import with `* as`. This is the most ES module-native approach and enables tree-shaking:
325+
326+
```typescript
327+
// service.ts
328+
export function fibo(number: number): number {
329+
if (number < 2) return number
330+
return fibo(number - 1) + fibo(number - 2)
331+
}
332+
```
333+
334+
```typescript
335+
// index.ts
336+
import { Elysia, t } from 'elysia'
337+
import * as Service from './service'
338+
339+
new Elysia()
340+
.get('/fibo', ({ body }) => {
341+
return Service.fibo(body)
342+
}, {
343+
body: t.Numeric()
344+
})
345+
```
346+
347+
</template>
348+
349+
<template v-slot:namespace>
350+
351+
TypeScript namespaces can hold both values and types with the same name, which is useful if you want to co-locate types with your service:
352+
353+
```typescript
354+
import { Elysia, t } from 'elysia'
355+
356+
namespace Service {
357+
export function fibo(number: number): number {
358+
if (number < 2) return number
359+
return Service.fibo(number - 1) + Service.fibo(number - 2)
360+
}
361+
}
362+
363+
new Elysia()
364+
.get('/fibo', ({ body }) => {
365+
return Service.fibo(body)
366+
}, {
367+
body: t.Numeric()
368+
})
369+
```
370+
371+
</template>
372+
373+
<template v-slot:class>
374+
375+
If you're coming from Java or C#, you may prefer using an abstract class with static methods:
284376

285377
```typescript
286378
import { Elysia, t } from 'elysia'
287379

288380
abstract class Service {
289381
static fibo(number: number): number {
290-
if(number < 2)
291-
return number
292-
382+
if (number < 2) return number
293383
return Service.fibo(number - 1) + Service.fibo(number - 2)
294384
}
295385
}
@@ -302,7 +392,11 @@ new Elysia()
302392
})
303393
```
304394

305-
If your service doesn't need to store a property, you may use `abstract class` and `static` instead to avoid allocating class instance.
395+
</template>
396+
397+
</Tab>
398+
399+
All four patterns produce the same runtime behavior. Choose based on your team's familiarity and whether you need to co-locate types.
306400

307401
### 2. Request dependent service as Elysia instance
308402

docs/key-concept.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,10 @@ const app = new Elysia()
326326
})
327327
```
328328

329+
::: tip
330+
This example uses an abstract class, but you can also use plain objects, module exports, or namespaces. See [Best Practice: Service patterns](/essential/best-practice.html#1-abstract-away-non-request-dependent-service) for alternatives.
331+
:::
332+
329333
See [Best practice: MVC Controller](/essential/best-practice.html#controller).
330334

331335
### TypeScript

0 commit comments

Comments
 (0)