Capy is an experimental real-time community platform built with Go and React. It provides a REST API, JWT authentication, WebSocket events for live updates, text and voice channels, PostgreSQL persistence, and LiveKit integration for voice rooms.
Note
Capy is under active development and is not production-ready. Defaults are optimized for local development.
- JWT authentication with access and refresh tokens
- Base-oriented communities with joined-base membership
- Text and voice channel support
- Real-time WebSocket event pipeline
- Message history retrieval for text channels
- LiveKit voice room token generation and webhook handling
- PostgreSQL storage with GORM auto-migrations
- Swagger/OpenAPI documentation
- React + TypeScript web client scaffold
- Docker setup for the API, PostgreSQL, and PgAdmin
capy
|-- cmd/ # API server and helper CLI entry points
|-- internal/api/ # Gin handlers, middleware, requests, responses
|-- internal/config/ # Environment-based application config
|-- internal/domain/ # Models and repository interfaces
|-- internal/service/ # Auth, base, channel, message, voice services
|-- internal/websocket/ # WebSocket manager, clients, event handlers
|-- pkg/database/ # PostgreSQL connection and migrations
|-- swagger_docs/ # Generated Swagger/OpenAPI files
`-- web/ # React + Vite frontend
The API is served from cmd/api/main.go with a /api/v1 base path. The web app currently targets http://localhost:9091/api/v1.
- Go 1.24+
- Node.js and npm
- PostgreSQL 17+ or Docker
- LiveKit credentials for voice-channel flows
swagCLI if you want to regenerate Swagger docs locally
Install swag:
go install github.com/swaggo/swag/cmd/swag@latestCreate or update .env in the project root:
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=capybara_user
DB_PASSWORD=capybara_password
DB_NAME=capybara_db
SERVER_PORT=9091
CAPYBARA_SERVER_PORT=9091
ENV=development
JWT_SECRET=change_me
REFRESH_SECRET=change_me_too
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=secret
LIVEKIT_URL=http://localhost:7880
PGADMIN_EMAIL=admin@example.com
PGADMIN_PASSWORD=admin
PGADMIN_PORT=5050Important
The Go config reads ENV, not ENVIRONMENT. Use strong JWT_SECRET and REFRESH_SECRET values outside local development.
With Docker Compose:
docker compose up -d postgres pgadminOr use your own PostgreSQL instance and match the .env values above.
go mod download
make runThe API starts on:
- Health check:
http://localhost:9091/api/health - Swagger UI:
http://localhost:9091/api/v1/swagger/index.html - API base URL:
http://localhost:9091/api/v1
cd web
npm install
npm run devVite will print the local web URL, usually http://localhost:5173.
Run the API and database services:
docker compose up -d postgres pgadmin capybara-apiWarning
docker-compose.yml also defines a capybara-web service, but web/Dockerfile is not present in this repository. Running the full Compose stack without adding that Dockerfile will fail.
Public endpoints:
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Health check |
POST |
/api/v1/auth/register |
Register and receive tokens |
POST |
/api/v1/auth/login |
Login with email/username and password |
POST |
/api/v1/auth/refresh |
Refresh token pair |
POST |
/api/v1/webhooks/livekit |
LiveKit webhook receiver |
Authenticated endpoints:
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/auth/check-session |
Validate current access token |
GET |
/api/v1/profile |
Get authenticated profile |
GET |
/api/v1/base |
List available bases |
GET |
/api/v1/base/joined |
List bases joined by the current user |
POST |
/api/v1/base/join |
Join a base |
GET |
/api/v1/base/joined-voice-channel |
Get the current joined voice channel |
GET |
/api/v1/base/{baseID}/channels |
List channels in a base |
POST |
/api/v1/base/{baseID}/channels |
Create a text or voice channel |
GET |
/api/v1/base/{baseID}/voice-channel-users |
List connected voice users |
GET |
/api/v1/base/{baseID}/channels/{channelID}/messages |
List channel messages |
POST |
/api/v1/base/{baseID}/channels/{channelID}/join-voice |
Create a LiveKit join token |
POST |
/api/v1/base/{baseID}/channels/{channelID}/leave-voice |
Leave the current voice channel |
Use Authorization: Bearer <access_token> for authenticated routes.
Connect with an access token query parameter:
ws://localhost:9091/api/v1/ws?token=<access_token>
Events use the shared shape:
{
"type": "NEW_TEXT_MESSAGE",
"timestamp": "2026-06-16T00:00:00Z",
"data": {}
}The domain model defines events for text/image/voice messages, channel changes, voice state, user presence, typing, and errors.
Common API commands:
make run # generate Swagger docs and run the API
make build # generate Swagger docs and build bin/api
make test # run Go tests
make swag # regenerate Swagger docs
make clean # remove built binariesCreate development data:
make create-user email="user@example.com" username="user1" password="password123"
make create-base username="user1" name="Base1" desc="My first base"Frontend commands:
cd web
npm run dev
npm run build
npm run lint
npm run preview- Database tables are migrated automatically on API startup.
- Channel types are
CHANNEL_TYPE_TEXTandCHANNEL_TYPE_VOICE. - Swagger docs are generated into
swagger_docs/; older generated files also exist undercmd/docs/. - The root
package.jsononly contains frontend-related dependencies; the runnable web scripts live inweb/package.json.