Quick start
Quick start
This guide assumes you have completed the Installation steps and have both the database and backend running.
Start the backend
pnpm dev
The server starts with hot-reload (tsx watch) on http://localhost:3001.
Start the frontend (optional)
To run the Vue 3 admin SPA with Vite HMR:
cd frontend
pnpm dev
The Vite dev server starts on http://localhost:5173 and proxies API calls to localhost:3001.
For most admin tasks you can simply open http://localhost:3001 and use the pre-built SPA from
frontend-dist/.
Bootstrap the superadmin
If ADMIN_EMAIL and ADMIN_PASSWORD are set in .env, a superadmin account is created automatically the first time the server starts:
[bootstrap] Superadmin created: admin@example.com
If those variables are absent, you must create the first user manually via the BetterAuth sign-up endpoint:
curl -X POST http://localhost:3001/api/auth/sign-up/email \
-H "Content-Type: application/json" \
-d '{"name":"Admin","email":"admin@example.com","password":"changeme123!"}'
Then promote the user to superadmin via the admin API (requires an existing admin session or direct DB update).
Register your first application
Applications are the OAuth 2.1 clients. Use the admin SPA or the REST API:
# Sign in first to get a session cookie
curl -c cookies.txt -X POST http://localhost:3001/api/auth/sign-in/email \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"changeme123!"}'
# Create an application
curl -b cookies.txt -X POST http://localhost:3001/api/admin/applications \
-H "Content-Type: application/json" \
-d '{
"name": "My App",
"slug": "my-app",
"redirectUris": ["http://localhost:8080/callback"],
"allowedScopes": ["openid","profile","email","roles"]
}'
The response includes clientId (equals the slug) and clientSecret. Store the secret — it is shown only once.
Perform an authorization flow
- Redirect the user's browser to the authorization endpoint:
http://localhost:3001/api/auth/oauth2/authorize
?response_type=code
&client_id=my-app
&redirect_uri=http://localhost:8080/callback
&scope=openid%20profile%20email%20roles
&code_challenge=<PKCE_S256_challenge>
&code_challenge_method=S256
&state=<random>
- The user signs in at
/login, optionally approves the consent screen at/oauth2/consent, and is redirected back toredirect_uriwith?code=…. - Exchange the code for tokens:
curl -X POST http://localhost:3001/api/auth/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=<code>" \
-d "redirect_uri=http://localhost:8080/callback" \
-d "client_id=my-app" \
-d "client_secret=<secret>" \
-d "code_verifier=<PKCE_verifier>"
Response:
{
"access_token": "...",
"id_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 3600
}
Decode the id_token to find the custom claims:
{
"sub": "<userId>",
"email": "user@example.com",
"roles": ["editor"],
"permissions": ["articles", "comments.write"],
"features": { "maxProjects": 10, "exportEnabled": true }
}