Imported from bigcapitalhq/bigcapital (
AGENTS.md). Install upstream withnpx skills add bigcapitalhq/bigcapital. Copyright stays with the author.
AGENTS.md
Repo overview
Bigcapital is a multi-tenant accounting SaaS. pnpm + Lerna monorepo (independent versioning).
| Package | Path | Role |
|---|---|---|
@bigcapital/server |
packages/server |
NestJS API + CLI. Multi-tenant with system DB + per-tenant DBs (MariaDB). Knex migrations, Objection.js ORM. |
@bigcapital/webapp |
packages/webapp |
React SPA. Vite, BlueprintJS, Redux, React Router v5. |
@bigcapital/utils |
shared/bigcapital-utils |
Shared utilities. tsup (CJS + ESM). |
@bigcapital/pdf-templates |
shared/pdf-templates |
PDF invoice/estimate templates. Webpack, Storybook. |
@bigcapital/email-components |
shared/email-components |
Email templates (react-email). Vite, Storybook. |
@bigcapital/sdk-ts |
shared/sdk-ts |
TypeScript client generated from OpenAPI spec. |
Dev setup
cp .env.example .env(root-level env is the canonical one for local dev)docker compose up -d— starts MariaDB (3306), Redis (6379), Gotenberg (9000), Garage/S3 (3900)pnpm installpnpm run build:server— must run before migrations (server CLI depends on built output)pnpm run system:migrate:latest— system DBpnpm run dev:server— NestJS on:3000(watch mode)pnpm run dev:webapp— Vite on:4000, proxies/apiand/socketto:3000
Common commands
Run from repo root unless noted.
| What | Command |
|---|---|
| Install deps | pnpm install |
| Start server (watch) | pnpm run dev:server |
| Start webapp (Vite, port 4000) | pnpm run dev:webapp |
| Build server + shared deps | pnpm run build:server |
| Build all shared packages | pnpm run build:shared |
| Build everything | pnpm run build |
| Typecheck all packages | pnpm run typecheck |
| Lint all | pnpm run lint (fix) / pnpm run lint:check (check only) |
| Format all | pnpm run format (fix) / pnpm run format:check (check only) |
| System DB migrations | pnpm run system:migrate:latest |
| Tenant DB migrations | pnpm run tenants:migrate:latest |
| Make new system migration | pnpm run system:migrate:make <name> |
| Make new tenant migration | pnpm run tenants:migrate:make <name> |
| Seed system DB | pnpm run system:seed:latest |
| Seed tenant DB | pnpm run tenants:seed:latest |
| Regenerate SDK from OpenAPI | pnpm run generate:sdk-types |
| Playwright e2e | pnpm run e2e:webapp |
| Server unit tests | cd packages/server && pnpm run test |
| Server e2e tests | cd packages/server && pnpm run test:e2e |
Key gotchas
- Build before migrate:
pnpm run build:serveris required before running any migration CLI commands. The server CLI entry (src/cli.ts) is compiled separately vianest-cli.jsonprojects config. - Dual migration systems: System DB (
packages/server/src/database/system/) and tenant DB (packages/server/src/database/tenant/) have separate migration/seeds directories and separate CLI commands. - Single root
.env: The root.env(copied from root.env.example) is the only env file. The server and its CLI resolve it from the repo root regardless of CWD (seepackages/server/src/common/config/env.ts). If apackages/server/.envexists it takes precedence over the root.env. - Webapp env prefixes: Only
VITE_,REACT_APP_, andPUBLIC_URLenv vars are exposed to the client (seevite.config.ts). - Server runs on
:3000, webapp dev server on:4000. The Vite config proxies/apiand/socketto the server. - Playwright selectors use
data-testIdattributes (notdata-testid). Global setup registers + onboards a test user via API, persists auth toe2e/.auth/user.json. - Server path alias:
@/*maps topackages/server/src/*(both intsconfig.jsonand JestmoduleNameMapper). - Webapp path alias:
@maps topackages/webapp/src(Vite resolve alias). - Commit linting: Husky enforces conventional commits via
commitlintoncommit-msghook. - Node version:
enginesspecifies 16.x/17.x/18.x, but CONTRIBUTING.md says 18.x. Use 18.x.
Data flow: server → sdk-ts → webapp
The full request/response cycle with case conversion at each layer:
Webapp (camelCase) → SDK request middleware → API wire (snake_case) → Server SerializeInterceptor.in → DTOs (camelCase)
Webapp (camelCase) ← SDK response middleware ← API wire (snake_case) ← Server SerializeInterceptor.out ← Service (camelCase)
- Server DB columns: snake_case (MariaDB/Objection.js).
- Server DTOs (
@ApiProperty): camelCase — used for internal class-validator validation only. SerializeInterceptor(global,packages/server/src/common/interceptors/serialize.interceptor.ts):- Inbound: converts request body/query from snake_case → camelCase so DTOs receive camelCase keys.
- Outbound: converts response from camelCase → snake_case before sending to client.
- Net effect: the API wire format is always snake_case.
- SDK request middleware (
shared/sdk-ts/src/middleware/snake-case-request-middleware.ts): enabled by default. Converts outgoing request body/query from camelCase → snake_case (to match what the server interceptor expects). - SDK response middleware (
shared/sdk-ts/src/middleware/camel-case-middleware.ts): disabled by default (disableCamelCaseTransform: true). Converts snake_case → camelCase when opted in. - Webapp consumption:
- SDK fetcher path (
useApiFetcher()inpackages/webapp/src/hooks/useRequest.tsx): most queries get snake_case data by default. Pass{ enableCamelCaseTransform: true }to get automatic camelCase. - Legacy axios path (
useApiRequest()): no automatic transform. UsetransformToCamelCase()/transfromToSnakeCase()from@/utilsmanually.
- SDK fetcher path (
Practical impact
- When writing new query hooks: use
useApiFetcher({ enableCamelCaseTransform: true })and work with camelCase. Types from@bigcapital/sdk-tsare camelCase. - When using legacy
apiRequest: responses are snake_case — calltransformToCamelCase(res.data)before using. - Database columns are always snake_case. When adding migrations, use snake_case. When writing Objection.js models, map to camelCase properties.
- New API endpoints: define DTOs with camelCase
@ApiPropertynames. TheSerializeInterceptorhandles the rest.
Architecture notes
- Multi-tenancy: Each tenant gets its own database.
TenantDBManagerhandles per-tenant DB connections. System DB stores tenant registry. - Server modules: Feature modules live in
packages/server/src/modules/<Feature>/. Each module typically has its own NestJS module, service, controller, and model. - ORM: Objection.js (built on Knex). Models in
packages/server/src/models/. - Auth: Passport.js with JWT strategy. Multi-tenant auth via
nestjs-cls(continuation-local storage). - Background jobs: Bull/BullMQ with Redis.
- Object storage: S3-compatible (Garage in dev) for attachments/documents. Configured via
S3_*env vars withS3_FORCE_PATH_STYLE=truefor Garage. - PDF generation: Gotenberg service (
:9000in dev) using the server'sGET /public/static files + pdf-templates. - SDK generation:
pnpm run generate:sdk-typesexports OpenAPI spec from server, generates TypeScript types viaopenapi-typescript, builds the SDK package. Run this after API schema changes. - License: AGPL for the open-source edition. Some modules under
modules/EE/may be enterprise-only.
PR workflow
Use gh CLI (GitHub CLI). Install: brew install gh (macOS), then gh auth login.
# Create a draft PR (uses conventional commit format, targets develop)
gh pr create --draft --title "type(scope): title" --body-file .github/pull_request_template.md --base develop
# List your PRs
gh pr list --author "@me"
# Convert draft to ready for review
gh pr ready <PR-NUMBER>
# Add reviewers
gh pr edit <PR-NUMBER> --add-reviewer user1,user2
- PR template:
.github/pull_request_template.md— always use it via--body-file. - Title format: conventional commits (
feat(scope):,fix(scope):,chore:, etc.). - Target branch:
develop(notmain). - All PR content must be in English.