Skip to content

Commit b417151

Browse files
authored
Merge pull request #18 from crashmax-dev/fastify-plugin
feat(packages): add `@stenodb/fastify`
2 parents 19275b1 + 047bcfd commit b417151

File tree

27 files changed

+858
-62
lines changed

27 files changed

+858
-62
lines changed

examples/with-fastify/nodemon.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"watch": [
3+
"src"
4+
],
5+
"ignore": ["database", "dist"],
6+
"exec": "cross-env TS_NODE_TRANSPILE_ONLY=true NODE_ENV=development node --no-warnings --loader ts-node/esm --enable-source-maps --trace-warnings --inspect=0.0.0.0:9234 --nolazy src/index.ts",
7+
"ext": "ts"
8+
}

examples/with-fastify/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "with-fastify",
3+
"type": "module",
4+
"private": true,
5+
"scripts": {
6+
"start": "pnpm build && node dist/index.js",
7+
"dev": "nodemon",
8+
"build": "rm -rf dist && tsc"
9+
},
10+
"dependencies": {
11+
"@stenodb/fastify": "workspace:*",
12+
"class-transformer": "0.5.1",
13+
"class-validator": "0.14.0",
14+
"fastify": "4.13.0",
15+
"reflect-metadata": "0.1.13"
16+
},
17+
"devDependencies": {
18+
"@types/node": "18.11.19"
19+
}
20+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Post } from '../../dto/posts.dto.js'
2+
import { User } from '../../dto/users.dto.js'
3+
import {
4+
userIdAndPostIdParamsSchema,
5+
userIdParamsSchema
6+
} from './posts.schema.js'
7+
import type { UserService } from '../users/users.service.js'
8+
import type { FastifyInstance } from 'fastify'
9+
10+
export function postsController(
11+
fastify: FastifyInstance,
12+
service: UserService
13+
): void {
14+
fastify.get('/', () => {
15+
return service.userPosts
16+
})
17+
18+
fastify.get<{ Params: { userId: number } }>(
19+
'/:userId',
20+
{ schema: userIdParamsSchema },
21+
(request, reply) => {
22+
const user = service.findUserById(request.params.userId)
23+
if (!user) return reply.status(404).send('User not found')
24+
return user.posts
25+
}
26+
)
27+
28+
fastify.get<{ Params: { userId: number; postId: number } }>(
29+
'/:userId/:postId',
30+
{ schema: userIdAndPostIdParamsSchema },
31+
(request, reply) => {
32+
const { userId, postId } = request.params
33+
const user = service.findUserById(userId)
34+
if (!user) return reply.status(404).send('User not found')
35+
36+
const post = user.findPost(postId)
37+
if (!post) return reply.status(404).send('Post not found')
38+
39+
return post
40+
}
41+
)
42+
43+
fastify.post<{ Body: Post; Params: User }>(
44+
'/:userId',
45+
{ schema: userIdParamsSchema },
46+
async (request, reply) => {
47+
const user = service.findUserById(request.params.userId)
48+
if (!user) return reply.status(404).send('User not found')
49+
50+
const post = new Post(
51+
user.postsLastId + 1,
52+
request.body.subject,
53+
new Date()
54+
)
55+
user.addPost(post)
56+
await service.write()
57+
58+
return post
59+
}
60+
)
61+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const userIdParamsSchema = {
2+
params: {
3+
userId: {
4+
$ref: 'User#/properties/userId'
5+
}
6+
}
7+
}
8+
9+
export const userIdAndPostIdParamsSchema = {
10+
params: {
11+
...userIdParamsSchema.params,
12+
postId: {
13+
$ref: 'Post#/properties/postId'
14+
}
15+
}
16+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { User } from '../../dto/users.dto.js'
2+
import { userIdParamsSchema } from '../posts/posts.schema.js'
3+
import { userBodySchema, userResponseSchema } from './users.schema.js'
4+
import type { UserService } from './users.service.js'
5+
import type { FastifyInstance } from 'fastify'
6+
7+
export function userController(
8+
fastify: FastifyInstance,
9+
service: UserService
10+
): void {
11+
fastify.get('/', () => {
12+
return service.users
13+
})
14+
15+
fastify.get<{ Params: User }>(
16+
'/:userId',
17+
{
18+
schema: userIdParamsSchema
19+
},
20+
(request, reply) => {
21+
const user = service.findUserById(request.params.userId)
22+
if (!user) return reply.status(404).send('User not found')
23+
24+
return {
25+
id: user.userId,
26+
username: user.username,
27+
age: user.age
28+
}
29+
}
30+
)
31+
32+
fastify.post<{ Body: User }>(
33+
'/',
34+
{ schema: userBodySchema },
35+
async (request, reply) => {
36+
const { username, age } = request.body
37+
const newUser = service.addUser(username, age)
38+
if (!newUser) return reply.status(400).send('Username exist')
39+
return newUser
40+
}
41+
)
42+
43+
fastify.delete<{ Params: User }>(
44+
'/:userId',
45+
{ schema: userResponseSchema },
46+
async (request, reply) => {
47+
const user = await service.deleteUser(request.params.userId)
48+
if (!user) return reply.status(404).send('User not found')
49+
return user
50+
}
51+
)
52+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const userBodySchema = {
2+
body: { $ref: 'User' }
3+
}
4+
5+
export const userResponseSchema = {
6+
response: {
7+
200: { $ref: 'User' }
8+
}
9+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { User, Users } from '../../dto/users.dto.js'
2+
import type { Steno } from '@stenodb/fastify'
3+
import type { FastifyInstance } from 'fastify'
4+
5+
export class UserService {
6+
#users: Steno.NodeProvider<Users>
7+
8+
constructor(private readonly fastify: FastifyInstance) {
9+
this.#users = this.fastify.steno.get<Users>('users')!
10+
}
11+
12+
get users(): User[] {
13+
return this.#users.data!.users
14+
}
15+
16+
get userPosts() {
17+
return this.users.map((user) => {
18+
return {
19+
id: user.userId,
20+
posts: user.posts
21+
}
22+
})
23+
}
24+
25+
get userLastId(): number {
26+
return this.users.at(-1)!.userId
27+
}
28+
29+
async write(): Promise<void> {
30+
await this.#users.write()
31+
}
32+
33+
async addUser(username: string, age: number): Promise<User | null> {
34+
if (this.findUserByName(username)) return null
35+
36+
const user = new User(this.userLastId + 1, username.toLowerCase(), age)
37+
this.users.push(user)
38+
await this.write()
39+
40+
return user
41+
}
42+
43+
findUserById(userId: number): User | undefined {
44+
return this.users.find((user) => user.userId === userId)
45+
}
46+
47+
findUserByName(userName: string): User | undefined {
48+
return this.users.find((user) => user.username == userName)
49+
}
50+
51+
async deleteUser(userId: number): Promise<User | null> {
52+
const user = this.findUserById(userId)
53+
if (user) {
54+
this.#users.data!.users = this.users.filter(
55+
(user) => user.userId !== userId
56+
)
57+
await this.write()
58+
return user
59+
}
60+
61+
return null
62+
}
63+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Exclude, Type } from 'class-transformer'
2+
import { IsNumber, IsString, Length } from 'class-validator'
3+
4+
export class Post {
5+
@Exclude({ toPlainOnly: true })
6+
@IsNumber()
7+
postId: number
8+
9+
@IsString()
10+
@Length(1, 128)
11+
subject: string
12+
13+
@Exclude({ toPlainOnly: true })
14+
@Type(() => Date)
15+
createdAt: Date
16+
17+
constructor(postId: number, subject: string, createdAt: Date) {
18+
this.postId = postId
19+
this.subject = subject
20+
this.createdAt = createdAt
21+
}
22+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Exclude, Type } from 'class-transformer'
2+
import { IsNumber, IsString, Length } from 'class-validator'
3+
import { Post } from './posts.dto.js'
4+
5+
export class Users {
6+
@Type(() => User)
7+
users: User[]
8+
9+
constructor(...users: User[]) {
10+
this.users = users
11+
}
12+
}
13+
14+
export class User {
15+
@Exclude({ toPlainOnly: true })
16+
@IsNumber()
17+
userId: number
18+
19+
@IsString()
20+
@Length(3, 20)
21+
username: string
22+
23+
@IsNumber()
24+
age: number
25+
26+
@Exclude({ toPlainOnly: true })
27+
@Type(() => Post)
28+
posts: Post[]
29+
30+
constructor(userId: number, username: string, age: number, ...posts: Post[]) {
31+
this.userId = userId
32+
this.username = username
33+
this.age = age
34+
this.posts = posts
35+
}
36+
37+
findPost(postId: number): Post | undefined {
38+
return this.posts.find((post) => post.postId === postId)
39+
}
40+
41+
addPost(post: Post) {
42+
this.posts.push(post)
43+
}
44+
45+
get postsLastId(): number {
46+
return this.posts.at(-1)!.postId
47+
}
48+
}

examples/with-fastify/src/index.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import 'reflect-metadata'
2+
import { dirname, resolve } from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
import { AsyncAdapter, FastifySteno } from '@stenodb/fastify'
5+
import Fastify from 'fastify'
6+
import { postsController } from './api/posts/posts.controller.js'
7+
import { userController } from './api/users/users.controller.js'
8+
import { UserService } from './api/users/users.service.js'
9+
import { Post } from './dto/posts.dto.js'
10+
import { User, Users } from './dto/users.dto.js'
11+
12+
const fastify = Fastify()
13+
14+
const usersInitialData = new Users(
15+
new User(1, 'john', 18, new Post(1, 'Lorem ipsum', new Date())),
16+
new User(2, 'alice', 23)
17+
)
18+
19+
const usersAdapter = new AsyncAdapter(
20+
'users',
21+
Users,
22+
usersInitialData
23+
)
24+
25+
fastify.register(FastifySteno, {
26+
path: resolve(dirname(fileURLToPath(import.meta.url)), '..', 'db'),
27+
entities: [User, Post],
28+
adapters: [usersAdapter]
29+
})
30+
31+
fastify.register(
32+
(instance, _, done) => {
33+
const userService = new UserService(fastify)
34+
35+
instance.register(
36+
(instance, _, done) => {
37+
userController(instance, userService)
38+
done()
39+
},
40+
{ prefix: '/users' }
41+
)
42+
43+
instance.register(
44+
(instance, _, done) => {
45+
postsController(instance, userService)
46+
done()
47+
},
48+
{ prefix: '/posts' }
49+
)
50+
51+
done()
52+
},
53+
{ prefix: '/api' }
54+
)
55+
56+
fastify.get('/schemas', () => {
57+
return fastify.getSchemas()
58+
})
59+
60+
fastify.listen({ host: '0.0.0.0', port: 3000 }, (err, address) => {
61+
if (err) throw err
62+
console.log(address)
63+
})

0 commit comments

Comments
 (0)