Skip to content

Commit 3fa28cd

Browse files
authored
Rename server to API and enhance Alchemy integration (#45)
* chore: rename apps/server to apps/api * chore: migrate to alchemy for api * feat: update alchemy configuration and scripts for API development * feat: enhance alchemy integration with state management and update environment configuration * feat: add GitHub Actions workflow for API deployment and integrate alchemy password * chore: remove bun.lock * feat: set NODE_ENV to production in deployment workflow * fix: update repository name in GitHub comment for preview deployment * feat: update pull request event types and fix commit SHA reference in preview deployment comment * feat: remove unused destroy and deploy scripts from package.json * feat: add health check endpoint to the API
1 parent 80b6bcf commit 3fa28cd

File tree

15 files changed

+2238
-9670
lines changed

15 files changed

+2238
-9670
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
name: API Deployment
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, closed]
6+
branches:
7+
- main
8+
paths:
9+
- "apps/api/**"
10+
- "packages/**"
11+
- "package.json"
12+
- "pnpm-lock.yaml"
13+
- "!**/*.md"
14+
- "!**/README*"
15+
16+
concurrency:
17+
group: preview-${{ github.event.pull_request.number }}-${{ github.sha }}
18+
cancel-in-progress: false
19+
20+
env:
21+
STAGE: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || (github.ref == 'refs/heads/main' && 'prod' || github.ref_name) }}
22+
23+
jobs:
24+
deploy:
25+
if: ${{ github.event.action != 'closed' }}
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: read
29+
pull-requests: write
30+
31+
steps:
32+
- uses: actions/checkout@v6
33+
34+
- name: Setup Node.js
35+
uses: actions/setup-node@v6
36+
with:
37+
node-version: 22
38+
39+
- name: Setup pnpm
40+
uses: pnpm/action-setup@v5
41+
with:
42+
version: "10.14.0"
43+
44+
- name: Cache pnpm store
45+
uses: actions/cache@v5
46+
with:
47+
path: ~/.pnpm-store
48+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
49+
restore-keys: |
50+
${{ runner.os }}-pnpm-store-
51+
52+
- name: Install dependencies
53+
run: pnpm install --frozen-lockfile
54+
55+
- name: Deploy
56+
working-directory: apps/api
57+
run: pnpm dlx alchemy deploy --stage ${{ env.STAGE }}
58+
env:
59+
ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
60+
ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
61+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
62+
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
63+
PULL_REQUEST: ${{ github.event.number }}
64+
GITHUB_SHA: ${{ github.sha }}
65+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66+
NODE_ENV: production
67+
68+
cleanup:
69+
runs-on: ubuntu-latest
70+
if: ${{ github.event_name == 'pull_request' && github.event.action == 'closed' }}
71+
permissions:
72+
id-token: write
73+
contents: read
74+
pull-requests: write
75+
76+
steps:
77+
- uses: actions/checkout@v6
78+
79+
- name: Setup Node.js
80+
uses: actions/setup-node@v6
81+
with:
82+
node-version: 22
83+
84+
- name: Setup pnpm
85+
uses: pnpm/action-setup@v5
86+
with:
87+
version: "10.14.0"
88+
89+
- name: Cache pnpm store
90+
uses: actions/cache@v5
91+
with:
92+
path: ~/.pnpm-store
93+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
94+
restore-keys: |
95+
${{ runner.os }}-pnpm-store-
96+
97+
- name: Install dependencies
98+
run: pnpm install --frozen-lockfile
99+
100+
- name: Safety Check
101+
run: |-
102+
if [ "${{ env.STAGE }}" = "prod" ]; then
103+
echo "ERROR: Cannot destroy prod environment in cleanup job"
104+
exit 1
105+
fi
106+
107+
- name: Destroy Preview Environment
108+
working-directory: apps/api
109+
run: pnpm dlx alchemy destroy --stage ${{ env.STAGE }}
110+
env:
111+
ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
112+
ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
113+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
114+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
115+
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
116+
PULL_REQUEST: ${{ github.event.number }}
117+
NODE_ENV: production

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,6 @@ $RECYCLE.BIN/
263263

264264
# Wrangler
265265
*.wrangler
266+
267+
# Alchemy
268+
*.alchemy

apps/api/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# openssl rand -base64 32
2+
ALCHEMY_STATE_TOKEN=your-generated-token-here
3+
ALCHEMY_PASSWORD=your-generated-password-here

apps/api/alchemy.run.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import alchemy from "alchemy";
2+
import { Worker } from "alchemy/cloudflare";
3+
import { GitHubComment } from "alchemy/github";
4+
import { CloudflareStateStore } from "alchemy/state";
5+
6+
const app = await alchemy("realm-api", {
7+
stateStore:
8+
process.env.NODE_ENV === "production"
9+
? (scope) =>
10+
new CloudflareStateStore(scope, {
11+
scriptName: "realm-api-state-store",
12+
})
13+
: undefined, // Uses default FileSystemStateStore
14+
password: process.env.ALCHEMY_PASSWORD,
15+
});
16+
17+
export const worker = await Worker("api", {
18+
name: "realm-api",
19+
entrypoint: "./src/index.ts",
20+
url: true,
21+
adopt: true,
22+
bindings: {},
23+
observability: {
24+
enabled: true,
25+
logs: {
26+
enabled: true,
27+
headSamplingRate: 1,
28+
invocationLogs: true,
29+
persist: true,
30+
},
31+
traces: {
32+
enabled: true,
33+
headSamplingRate: 1,
34+
persist: true,
35+
},
36+
},
37+
domains: ["api.ahargunyllib.dev"],
38+
dev: {
39+
port: 3000,
40+
},
41+
});
42+
43+
if (process.env.PULL_REQUEST) {
44+
// if this is a PR, add a comment to the PR with the preview URL
45+
// it will auto-update with each push
46+
await GitHubComment("preview-comment", {
47+
owner: "ahargunyllib",
48+
repository: "realm",
49+
issueNumber: Number(process.env.PULL_REQUEST),
50+
body: `### Preview Deployment
51+
52+
**Commit:** \`${process.env.GITHUB_SHA}\`
53+
**Preview URL:** ${worker.url}
54+
**Deployed at:** ${new Date().toUTCString()}`,
55+
});
56+
}
57+
58+
await app.finalize();

apps/api/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "api",
3+
"type": "module",
4+
"version": "0.0.1",
5+
"scripts": {
6+
"typecheck": "tsc --noEmit",
7+
"dev": "alchemy dev --app realm-api"
8+
},
9+
"dependencies": {
10+
"alchemy": "catalog:",
11+
"hono": "^4.7.9"
12+
},
13+
"devDependencies": {
14+
"@realm/tsconfig": "workspace:*",
15+
"typescript": "catalog:"
16+
}
17+
}

apps/api/src/env.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { worker } from "../alchemy.run";
2+
3+
export type Env = typeof worker.bindings;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import { Hono } from "hono";
33
const app = new Hono();
44

55
app.get("/", (c) => c.text("Hello World"));
6+
app.get("/health", (c) => c.json({ status: "ok" }));
67

78
export default app;

apps/server/package.json

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)