+54
-8
README.md
+54
-8
README.md
···
1
1
# Statusphere React
2
2
3
-
A monorepo for the Statusphere application, which includes a React client and a Node.js backend.
3
+
A status sharing application built with React and the AT Protocol.
4
4
5
-
This is a React refactoring of the [example application](https://atproto.com/guides/applications) covering:
5
+
This is a React implementation of the [example application](https://atproto.com/guides/applications) covering:
6
6
7
7
- Signin via OAuth
8
8
- Fetch information about users (profiles)
···
42
42
### Additional Commands
43
43
44
44
```bash
45
-
# Build both packages
46
-
pnpm build
45
+
# Build commands
46
+
pnpm build # Build frontend first, then backend
47
+
pnpm build:appview # Build only the backend
48
+
pnpm build:client # Build only the frontend
49
+
50
+
# Start commands
51
+
pnpm start # Start the server (serves API and frontend)
52
+
pnpm start:client # Start frontend development server only
53
+
pnpm start:dev # Start both backend and frontend separately (development only)
54
+
55
+
# Other utilities
56
+
pnpm typecheck # Run type checking
57
+
pnpm format # Format all code
58
+
```
59
+
60
+
## Deployment
47
61
48
-
# Run typecheck on both packages
49
-
pnpm typecheck
62
+
For production deployment:
50
63
51
-
# Format all code
52
-
pnpm format
64
+
1. Build both packages:
65
+
```bash
66
+
pnpm build
67
+
```
68
+
69
+
This will:
70
+
- Build the frontend (`packages/client`) first
71
+
- Then build the backend (`packages/appview`)
72
+
73
+
2. Start the server:
74
+
```bash
75
+
pnpm start
76
+
```
77
+
78
+
The backend server will:
79
+
- Serve the API at `/api/*` endpoints
80
+
- Serve the frontend static files from the client's build directory
81
+
- Handle client-side routing by serving index.html for all non-API routes
82
+
83
+
This simplifies deployment to a single process that handles both the API and serves the frontend assets.
84
+
85
+
## Environment Variables
86
+
87
+
Create a `.env` file in the root directory with:
88
+
89
+
```
90
+
# Required for AT Protocol authentication
91
+
ATP_SERVICE_DID=did:plc:your-service-did
92
+
ATP_CLIENT_ID=your-client-id
93
+
ATP_CLIENT_SECRET=your-client-secret
94
+
ATP_REDIRECT_URI=https://your-domain.com/oauth-callback
95
+
96
+
# Optional
97
+
PORT=3001
98
+
SESSION_SECRET=your-session-secret
53
99
```
54
100
55
101
## Requirements
+10
-2
package.json
+10
-2
package.json
···
11
11
"dev:client": "pnpm --filter @statusphere/client dev",
12
12
"dev:oauth": "node scripts/setup-ngrok.js",
13
13
"lexgen": "pnpm --filter @statusphere/lexicon build",
14
-
"build": "pnpm -r build",
15
-
"start": "pnpm -r start",
14
+
15
+
"build": "pnpm build:client && pnpm build:appview",
16
+
"build:appview": "pnpm --filter @statusphere/appview build",
17
+
"build:client": "pnpm --filter @statusphere/client build",
18
+
19
+
"start": "pnpm --filter @statusphere/appview start",
20
+
"start:dev": "pnpm -r start",
21
+
"start:appview": "pnpm --filter @statusphere/appview start",
22
+
"start:client": "pnpm --filter @statusphere/client start",
23
+
16
24
"clean": "pnpm -r clean",
17
25
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
18
26
"typecheck": "pnpm -r typecheck"
+31
-4
packages/appview/src/index.ts
+31
-4
packages/appview/src/index.ts
···
1
1
import events from 'node:events'
2
2
import type http from 'node:http'
3
+
import path from 'node:path'
3
4
import type { OAuthClient } from '@atproto/oauth-client-node'
4
5
import { Firehose } from '@atproto/sync'
5
6
import cors from 'cors'
···
17
18
import { createIngester } from '#/ingester'
18
19
import { env } from '#/lib/env'
19
20
import { createRouter } from '#/routes'
21
+
import fs from 'node:fs'
20
22
21
23
// Application state passed to the router and elsewhere
22
24
export type AppContext = {
···
119
121
const router = createRouter(ctx)
120
122
app.use(express.json())
121
123
app.use(express.urlencoded({ extended: true }))
122
-
app.use(router)
123
-
app.use('*', (_req, res) => {
124
-
res.sendStatus(404)
125
-
})
124
+
125
+
// API routes
126
+
app.use('/api', router)
127
+
128
+
// Serve static files from the frontend build
129
+
const frontendPath = path.resolve(__dirname, '../../../client/dist')
130
+
131
+
// Check if the frontend build exists
132
+
if (fs.existsSync(frontendPath)) {
133
+
logger.info(`Serving frontend static files from: ${frontendPath}`)
134
+
135
+
// Serve static files
136
+
app.use(express.static(frontendPath))
137
+
138
+
// For any other requests, send the index.html file
139
+
app.get('*', (req, res) => {
140
+
// Skip API routes
141
+
if (req.path.startsWith('/api/')) {
142
+
return res.sendStatus(404)
143
+
}
144
+
145
+
res.sendFile(path.join(frontendPath, 'index.html'))
146
+
})
147
+
} else {
148
+
logger.warn(`Frontend build not found at: ${frontendPath}`)
149
+
app.use('*', (_req, res) => {
150
+
res.sendStatus(404)
151
+
})
152
+
}
126
153
127
154
// Use the port from env (should be 3001 for the API server)
128
155
const server = app.listen(env.PORT)
+1
packages/client/package.json
+1
packages/client/package.json