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