Scratch space for learning atproto app development
1import events from 'node:events'
2import type http from 'node:http'
3import cors from 'cors'
4import express, { type Express } from 'express'
5import helmet from 'helmet'
6import { pino } from 'pino'
7
8import { createDb, migrateToLatest } from '#/db'
9import { env } from '#/env'
10import { Ingester } from '#/firehose/ingester'
11import errorHandler from '#/middleware/errorHandler'
12import requestLogger from '#/middleware/requestLogger'
13import { createRouter } from '#/routes'
14import type { AppContext } from './config'
15
16export class Server {
17 constructor(
18 public app: express.Application,
19 public server: http.Server,
20 public ctx: AppContext,
21 ) {}
22
23 static async create() {
24 const { NODE_ENV, HOST, PORT } = env
25
26 const logger = pino({ name: 'server start' })
27 const db = createDb(':memory:')
28 await migrateToLatest(db)
29 const ingester = new Ingester(db)
30 ingester.start()
31 const ctx = {
32 db,
33 ingester,
34 logger,
35 }
36
37 const app: Express = express()
38
39 // Set the application to trust the reverse proxy
40 app.set('trust proxy', true)
41
42 // TODO: middleware for sqlite server
43 // TODO: middleware for OAuth
44
45 // Middlewares
46 app.use(express.json())
47 app.use(express.urlencoded({ extended: true }))
48 app.use(cors({ origin: env.CORS_ORIGIN, credentials: true }))
49 app.use(helmet())
50
51 // Request logging
52 app.use(requestLogger)
53
54 // Routes
55 const router = createRouter(ctx)
56 app.use(router)
57
58 // Error handlers
59 app.use(errorHandler())
60
61 const server = app.listen(env.PORT)
62 await events.once(server, 'listening')
63 logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`)
64
65 return new Server(app, server, ctx)
66 }
67
68 async close() {
69 this.ctx.logger.info('sigint received, shutting down')
70 this.ctx.ingester.destroy()
71 return new Promise<void>((resolve) => {
72 this.server.close(() => {
73 this.ctx.logger.info('server closed')
74 resolve()
75 })
76 })
77 }
78}