About This Guide
Caution Nexa BaaS has not been publicly released yet. The repository is private, and there are no downloads, npm packages, or hosted service. This guide outlines the intended workflow based on the features implemented so far; API paths, setting names, and procedures may change at public release.
Nexa BaaS is a self-hosted Backend-as-a-Service for mobile and web app developers. It provides, in a single server, the authentication, permissions, data storage, file uploads, scheduled push notifications, and offline sync that you would otherwise rebuild for every app, and you run it yourself on Docker.
This guide is written for:
- App developers building on Nexa BaaS — how to use the console, projects, SDK, and REST API
- Operators who want to run the server themselves — the self-hosting procedure and environment variables
For a product overview, see the app introduction page.
Concepts and Architecture
Key Concepts
| Term | Meaning |
|---|---|
| Developer account | The account you use to sign in to the developer console. Internally, it is a user of the reserved platform tenant |
| Project | An isolated backend space (= tenant) for a single app. One developer account can own several |
| Project ID (tenantId) | The UUID that identifies a project. Used in SDK and API calls |
| App user | An end user who signed up to a given project. Each project has its own user pool (the same email is a separate account in each project) |
| Collection | A set of records (JSON) in the Data API. Declares access rules, whether sync is enabled, and unique constraints |
| Bucket | A set of files in file storage. Uses access rules with the same syntax as collections |
Data in different projects is isolated at the database level by PostgreSQL Row Level Security (RLS), and every request runs inside a transaction with that project’s context set.
Services (12)
| Service | What it provides | Main access point |
|---|---|---|
| Authentication | Email-verified sign-up (3 approval modes), sign-in and token refresh, password reset, MFA (TOTP), Google sign-in | SDK, REST |
| Users, groups, permissions (RBAC) | User management, groups and memberships, resource:action permissions (deny-first evaluation) | Console, admin API |
| Data API | JSON record CRUD, filtering, sorting, paging, declarative access rules, unique constraints | SDK, console |
| File storage | Presigned direct uploads, large resumable multipart uploads, signed download URLs | SDK |
| Reminders (scheduled push) | Device registration, scheduling and cancelling pushes at a chosen time | SDK |
| Offline sync | Revision-based delta pull and batch push, 3 conflict policies | SDK |
| Webhooks | Event outbox publishing, HMAC-signed delivery with retries, Slack, Telegram, and KakaoWork connectors | Console, admin API |
| Developer console | Project creation; screens for users, data, webhooks, OAuth, logs, and usage | Web |
| Plan limits | Automatic blocking at per-plan user-count and storage limits | Automatic on the server |
| Usage metering | Daily aggregation of data, file, and sync calls | Console |
| Log management | Per-category audit log settings and queries | Console, admin API |
| Per-project OAuth (BYO) | Registration of developer-owned Google credentials (stored encrypted) | Console |
Components
| Component | Description | Default port |
|---|---|---|
| API server | NestJS application. All REST API paths use the /api/v1 prefix (except health checks) | 3000 |
| Developer console | Vite + React web app | 5174 |
| Example app Nexa Words | An offline-first vocabulary app built with the SDK (for reference) | 5173 |
| PostgreSQL | Primary database (required) | 5432 |
| Redis (Valkey) | Job queue for scheduled push, webhook delivery, and maintenance jobs (optional) | 6379 |
| S3-compatible object storage | File storage (optional, e.g. MinIO) | 9000 |
Quick Start
The overall flow is:
- Start the server — see Self-Hosting.
- Create a developer account and sign in to the console.
- Create a project in the console and note its Project ID.
- Create a collection and its access rules on the console’s Data screen.
- In your app, give the SDK the server address and project ID, sign up and sign in an app user, then read and write data.
- Use the console to approve or block sign-ups and to check logs and usage.
Developer Console
Creating a Developer Account
The console does not have a sign-up form yet. A developer account is created by going through the regular sign-up flow against the platform tenant. The platform tenant ID is set with the server environment variable PLATFORM_TENANT_ID; if unset, the reserved UUID 00000000-0000-4000-8000-000000000000 is used.
# ① Request sign-up — sends a verification email (always responds 202 and never reveals whether the account exists)
curl -X POST http://localhost:3000/api/v1/auth/signup/request \
-H 'content-type: application/json' \
-H 'x-tenant-id: <platform tenant ID>' \
-d '{"email":"[email protected]"}'
# ② Verify with the token from the email link → receive signupSessionToken
curl -X POST http://localhost:3000/api/v1/auth/signup/verify \
-H 'content-type: application/json' -H 'x-tenant-id: <platform tenant ID>' \
-d '{"token":"<token from the email>"}'
# ③ Set a password to complete sign-up
curl -X POST http://localhost:3000/api/v1/auth/signup/complete \
-H 'content-type: application/json' -H 'x-tenant-id: <platform tenant ID>' \
-d '{"signupSessionToken":"<value from ②>","password":"<password>","passwordConfirm":"<password>"}'
Note If the mail setting (
MAIL_PROVIDER) is left at its defaultfake, no real email is sent; instead, the verification link is printed to the API server’s console log. When testing locally, get the token from the log.
Signing In and Screen Layout
Sign in with your developer account in the console (default http://localhost:5174). The API address the console connects to and the platform tenant ID can be changed with the console build environment variables VITE_NEXA_BASE_URL (default http://localhost:3000) and VITE_NEXA_PLATFORM_TENANT_ID.
- Left sidebar — Dashboard and Projects; once a project is selected, also Data, Files, Reminders, Webhooks, Social Login, Usage, Logs, and Users
- Top bar — the current project, the developer’s email, and sign-out
- Dashboard — feature catalog cards (what you can do and whether the console supports it). With a project selected, you can open each feature for that project.
Creating a Project and the Project ID
- On the Projects screen, enter a name (1–100 characters) and click + Create.
- Select a project in the list; all subsequent screens then work on that project.
- The project’s ID (UUID) is the SDK’s
tenantIdand the API’sX-Tenant-Idvalue.
Note Per-project API keys are not issued at this time. Apps (clients) call the API with the project ID and the app user’s sign-in token (JWT), and administrative tasks are done in the console (with a developer token). In the pre-release design, an API key scheme is left as follow-up work. Deleting or transferring projects is not available yet either.
Users Screen
View and manage the app users who signed up to the project, by status.
- Approve / Reject — handle pending sign-ups in pre-approval mode
- Block / Unblock — suspend and restore accounts
- Reset password — force-send a password reset email to the user (no temporary password is created)
- Delete account — soft delete. After a default 30-day grace period the email is anonymized, so the same email can sign up again
Data Screen
A playground where you can create collections and insert records directly.
- Collection names consist of the characters
[a-z0-9_-]. - When creating one, you specify access rules (
rules), offline sync (sync) with its conflict policy, and unique constraints (uniqueBy). - Records are created and deleted by editing JSON. Deleting a collection also deletes all of its records.
Webhooks Screen
Register a webhook by choosing the receiving URL, the events to receive, and the format (generic, slack, telegram, kakaowork). Telegram additionally requires a chatId. In Delivery history below, you can check status and attempt counts and requeue failed deliveries. See Webhooks for details.
Social Login (BYO) Screen
Register Google OAuth credentials dedicated to the project, and the Google consent screen shows the developer’s app name.
- Create an OAuth client in the Google Cloud Console.
- Register the app name, Client ID, and Client Secret in the console.
- Add the callback URI shown in the registration response to Authorized redirect URIs in the Google Cloud Console.
Deleting the credentials falls back to the server-wide shared credentials (if configured). Registration requires the credential encryption key (CREDENTIALS_ENCRYPTION_KEY) to be set on the server.
Usage and Logs Screens
- Usage — view daily aggregates of data, file, and sync calls over 7-, 30-, or 90-day ranges.
- Logs — turn audit logging on or off per category (
auth,admin,user,data,file,reminder,hook) and query by period and user.authandadminare for security auditing and cannot be turned off.
Files and Reminders Screens
These two features can only be called with an app user token, so the console shows guidance only. Test them with the SDK or the REST API.
Client SDK
Installation and Initialization
@nexa/sdk is a TypeScript client that runs on the web, React Native, and Node, with no runtime dependencies (it uses only fetch and crypto).
Caution The SDK has not been published to npm yet. For now it is available only as a workspace package inside the repository; npm publication is deferred until the service goes public.
import { createClient } from '@nexa/sdk';
const nexa = createClient({
baseUrl: 'https://api.example.com', // the SDK adds the '/api/v1' prefix
tenantId: '<project ID>',
// store: if omitted, stored in memory (lost when the app exits)
// syncIntervalMs: 30_000, // auto-sync interval for sync collections (optional)
});
Sign-In and Sessions
await nexa.auth.login('[email protected]', 'password');
nexa.auth.onAuthLost(() => {
// the server rejected the session — send the user to the sign-in screen
});
await nexa.auth.logout();
- Tokens are saved in the configured store and restored when the app restarts.
- When the API returns 401, the SDK refreshes the token once and resends the original request.
- A refresh that fails because the network is down is not treated as a lost session.
onAuthLostis called only when the server rejects the session. - App user sign-up is done through the REST API, not an SDK method (see Authentication).
Caution For accounts with MFA enabled, the SDK’s
login()throws anAUTH_MFA_REQUIREDerror. The SDK does not currently support the MFA sign-in step.
Online Collections
const memos = nexa.collection<{ title: string; done?: boolean }>('memos');
const created = await memos.create({ title: 'First memo' });
await memos.update(created.id, { done: true }); // merged per top-level key
const page = await memos.list({
filter: { done: true, 'meta.count': { gte: 3 }, tag: { in: ['a', 'b'] } },
sort: '-updated_at',
page: 1,
pageSize: 20,
});
await memos.remove(created.id);
Every call goes straight to the server. On failure, a NexaError(code, message, status) carrying the server error code is thrown.
Offline-First (sync) Collections
The collection must be created on the server with sync: true.
const words = nexa.collection<{ term: string; memo?: string }>('words', {
sync: true,
onConflict: (r) => console.log('Item merged/rejected by the server', r.id, r.error),
});
const w = await words.put({ term: 'apple' }); // saved locally right away; id is an auto-generated UUID
const items = await words.all(); // local snapshot
const off = words.subscribe((items) => render(items));
const { pushed, pulled, resetOccurred } = await words.sync(); // push my changes → pull others' changes
- All reads and writes are handled locally and immediately, so they work the same offline.
- Editing the same record several times collapses into a single pending change.
- Conflicts follow the result of the server’s conflict policy as-is. Merged or rejected items are replaced with the server’s value and reported through
onConflict. - Sync is triggered in three ways: manual
sync(), thesyncIntervalMsinterval, and the browseronlineevent. Callnexa.dispose()when the app shuts down.
Persistent Stores
The default memory store is lost when the process ends. To keep sign-in state and offline data across app restarts, inject a store.
// Web — IndexedDB
import { createClient, IndexedDBLocalStore } from '@nexa/sdk';
const nexa = createClient({ baseUrl, tenantId, store: new IndexedDBLocalStore() });
// React Native / Node — wrap any SQLite library in 2 methods and inject it
import { createClient, SqlLocalStore, type ISqlDriver } from '@nexa/sdk';
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('nexa.db');
const driver: ISqlDriver = {
run: async (sql, params) => void (await db.runAsync(sql, ...(params as []))),
all: async (sql, params) => db.getAllAsync(sql, ...(params as [])),
};
const nexa = createClient({ baseUrl, tenantId, store: new SqlLocalStore(driver) });
File and Reminder Helpers
// Files — direct upload that bypasses the server. Switches to multipart automatically above 32MiB
const docs = nexa.files('docs');
const file = await docs.upload(blob, { name: 'note.txt' });
window.open(file.downloadUrl); // signed URL valid for 5 minutes
await docs.list();
await docs.remove(file.id);
// Register a device + schedule a reminder
await nexa.devices.register(fcmToken, 'fcm');
const r = await nexa.reminders.schedule({
sendAt: new Date(Date.now() + 60_000),
payload: { title: 'Time to review' },
});
await nexa.reminders.cancel(r.id);
Using Each Service
Common Rules
- Path prefix:
/api/v1(except the health checks/healthand/health/ready) - Project selection: the token’s project if a sign-in token is present, otherwise the
X-Tenant-Idheader - Authentication:
Authorization: Bearer <access token> - Response format: success is
{ "success": true, "data": … }(withmetafor lists), failure is{ "success": false, "error": { "code", "message" } } - Every response carries an
X-Trace-Idheader for tracing.
Authentication
| Feature | Path |
|---|---|
| Sign-up request → verify → complete | POST /auth/signup/request → POST /auth/signup/verify → POST /auth/signup/complete |
| Sign-in / MFA second step | POST /auth/login, POST /auth/login/mfa |
| Token refresh / sign-out | POST /auth/refresh, POST /auth/logout |
| Password reset | POST /auth/password/reset/request → /verify → /complete |
| My profile | GET·PATCH /me, POST /me/password, POST /me/password/set |
| MFA (TOTP) | POST /me/mfa/totp/enroll → POST /me/mfa/totp/activate, DELETE /me/mfa/totp |
| Google sign-in | GET /auth/oauth/… (requires server-wide or per-project Google credentials) |
- Sign-up verifies the email first, so no account is created until the link in the email is confirmed. Verification links are valid for 10 minutes by default.
- There are three approval modes, set per project: immediate full access (
AUTO_FULL), basic access with some permissions requiring approval (AUTO_BASIC), and admin pre-approval (PRE_APPROVAL). - Passwords go through a strength check and are checked against a list of breached passwords.
- Access tokens last 15 minutes and refresh tokens 14 days by default (configurable on the server). Refresh tokens are rotated on every use, and if reuse is detected, all sessions are invalidated.
- A Google account is automatically linked to an existing account with the same email only if the email is verified.
Users, Groups, and Permissions
The admin API is called with a console developer token (Authorization + the target project’s X-Tenant-Id).
- Users:
/admin/users— list, details, update; approve, block, unblock, delete; password reset email - Groups:
/admin/groups— CRUD,POST /admin/groups/:id/members,DELETE /admin/groups/:id/members/:userId - Permissions:
/admin/permissions(definitions), group grants, per-user allow/deny overrides - Permission keys use the
resource:actionformat, evaluated in the order user deny > user allow > group grant > default deny.
Data API
Creating a collection (admin API or the console Data screen):
curl -X POST http://localhost:3000/api/v1/admin/collections \
-H 'authorization: Bearer <developer token>' -H 'x-tenant-id: <project ID>' \
-H 'content-type: application/json' \
-d '{"name":"memos","rules":{"read":["owner"],"create":["authenticated"],"update":["owner"],"delete":["owner"]}}'
Access rules have four kinds of principals.
| Principal | Meaning |
|---|---|
public | Also allows requests without sign-in (for public reads) |
authenticated | A signed-in app user |
owner | The user who created the record. List queries are automatically filtered to the user’s own records |
permission:<key> | Users who have the RBAC permission |
- Actions are
read,create,update, anddelete; any action not listed in the rules is denied. - Record API:
POST·GET /data/:collection,GET·PATCH·DELETE /data/:collection/:id - List queries:
filter[field][op]=value(operatorseq,ne,gt,gte,lt,lte,in,like; dot-notation paths allowed),sort=-updated_at,pageandpageSize(default 20, max 100) - Writes always require sign-in. Calls without sign-in need the
X-Tenant-Idheader and onlypublicreads are allowed. - You can prevent duplicates on a combination of 1–3 top-level fields, e.g.
uniqueBy: ["term"](violations returnDATA_006). - Limits: 64KB per record, 100 collections per project, 10 filters. Deleting a record is immediate and permanent.
File Storage
- Bucket management:
/admin/file-buckets— rules areread(download),create(upload), anddelete - Upload flow:
POST /files/:bucket(prepare upload; issues an upload URL valid for 15 minutes) → the client PUTs directly to storage →POST /files/:bucket/:id/complete(size is checked, then the upload is finalized) - Retrieval:
GET /files/:bucket/:id(includes a download URL valid for 5 minutes),GET /files/:bucket(list),DELETE /files/:bucket/:id - Large files:
multipart: truein the prepare request (up to 5GiB, 8MiB parts). Requesting part URLs again lets you resume, andabortcancels. - Single uploads are limited to 32MiB. Uploads not completed within 24 hours are cleaned up automatically.
Reminders (Scheduled Push)
- Devices:
POST /me/devices(the same token is updated),GET /me/devices,DELETE /me/devices/:id - Scheduling:
POST /reminders { sendAt, payload: { title, body, data } }—sendAtmust be between 30 seconds and 1 year from now - Query and cancel:
GET /reminders?status=,DELETE /reminders/:id(only while scheduled) - Limits: 100 characters for the title, 500 for the body, 4KB for data, and 500 pending reminders per user.
- Redis is required for reminders to actually be sent. Real push delivery requires FCM configuration (
PUSH_PROVIDER=fcm); the default isfake, which only writes to the server log.
Offline Sync
- Create the collection with
sync: trueand choose a conflict policy (conflictPolicy).
| Policy | Behavior |
|---|---|
lww (default) | The later write wins |
fieldMerge | Merges per top-level field |
serverWins | Rejects the conflicting write and returns the server value |
- Pull:
GET /sync/:collection?since=<seq>&limit=(default 200, max 1000) - Push:
POST /sync/:collection/push(up to 200 items at a time, partial success per item) — record ids are client-generated UUID v4s - Deletions propagate as markers (tombstones) and are cleaned up after 90 days. Pulling from a position that is too old returns
resetRequired: true, and everything is pulled again (handled automatically by the SDK). - Sync requires sign-in; with the SDK you never need to handle the protocol above directly.
Webhooks
- Registration:
/admin/webhooks(10 per project). The signing secret is shown only once at registration, so store it right away. - Events:
record.created,record.updated,record.deleted,user.registered,file.uploaded,file.deleted,reminder.sent - The
genericformat is sent with anX-Nexa-Signature: sha256=<HMAC-SHA256 of the body>signature, plusX-Nexa-Eventand anX-Nexa-Deliveryheader for deduplication. Verify the signature on the receiving server. - On failure, deliveries are retried at intervals of 1 minute → 5 minutes → 30 minutes → 2 hours → 12 hours, and are marked as failed after more than 5 attempts. History:
GET /admin/webhooks/events; resend:POST /admin/webhooks/events/:id/retry. - Delivery is handled by a Redis-based worker roughly every minute. Without Redis, deliveries stay pending.
Plan Limits, Usage, and Logs
- Per-plan limits (the server currently enforces only the user count and storage capacity):
| Plan | Users | Storage |
|---|---|---|
| free | 100 | 1GiB |
| starter | 1,000 | 10GiB |
| growth | 10,000 | 100GiB |
| enterprise | Unlimited | Unlimited |
- Usage:
GET /console/projects/:tenantId/usage?from=&to=(default: last 30 days) - Logs:
GET /admin/logs; settings viaGET·PUT /admin/logs/settings - Major APIs are rate-limited to about 120 requests per minute (per project × user); exceeding it returns
CMN_004.
Common Error Codes
| Code | Meaning |
|---|---|
AUTH_002 | Email not verified, or token invalid or expired |
AUTH_003 | Invalid credentials |
USER_002 | Permission denied (access rule or RBAC denial) |
DATA_001 / DATA_005 | Collection not found / record not found |
DATA_004 | Record size exceeded (64KB) |
DATA_006 | Unique constraint violation |
FILE_004 / FILE_005 | File size exceeded / invalid upload state |
RMD_002 / RMD_003 | Send time out of range / cannot be cancelled in this state |
SYNC_001 | Sync is not enabled for the collection |
CON_001 / CON_002 | Not a console account / project not found or not owned |
CMN_002 / CMN_004 | Limit exceeded / too many requests |
Self-Hosting
Caution The procedure below is the development and verification setup documented in the repository. There is no production deployment guide yet (TLS, backups, scaling, etc.), and until the repository is made public you need repository access to follow it.
Prerequisites
| Level | What you need | What you can do |
|---|---|---|
| Minimal | Node.js 22, pnpm (corepack) | Build and test, start the server and health checks |
| Basic | + PostgreSQL connection details | Use most features via the console and SDK |
| Full | + Docker (PostgreSQL, Redis, MinIO) | Queue features such as scheduled push and webhook delivery |
| Production | + mail, FCM, and Google credentials | Real email, push, and social sign-in |
Installation and Startup
corepack enable pnpm # once, the first time
pnpm install
docker compose up -d postgres redis minio # local infrastructure
cp apps/api/.env.example apps/api/.env # prepare environment variables, then fill in values
pnpm --filter @nexa/shared build # once, the first time
pnpm --filter @nexa/api migrate # apply DB migrations
pnpm dev # run API, console, and example app together
- Run individually:
pnpm --filter @nexa/api dev(API :3000),pnpm --filter nexa-console dev(console :5174),pnpm --filter example-words dev(example app :5173) - Health checks:
GET /health(liveness),GET /health/ready(checks the DB connection) - To run the API in a container as well, use
docker compose --profile full up -d. - To use the breached-password list, load it beforehand with
pnpm --filter @nexa/api seed:breached.
Key Environment Variables
| Variable | Description |
|---|---|
DATABASE_URL | PostgreSQL connection string (required for DB features) |
JWT_SECRET | Token signing key (16+ characters, required in production) |
ACCESS_TOKEN_TTL_SECONDS / REFRESH_TOKEN_TTL_SECONDS | Token lifetimes (default 900 seconds / 14 days) |
APP_BASE_URL | Base address used for links in authentication emails |
MAIL_PROVIDER · MAIL_FROM | Mail delivery method (fake by default, or resend) and sender address |
REDIS_URL | Queue features (scheduled push, webhooks, maintenance jobs) |
STORAGE_ENDPOINT · STORAGE_ACCESS_KEY · STORAGE_SECRET_KEY · STORAGE_BUCKET · STORAGE_REGION | S3-compatible object storage |
PUSH_PROVIDER · FCM_SERVICE_ACCOUNT_JSON | Push delivery method (fake by default, or fcm) and FCM service account (base64) |
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRET | Server-wide Google sign-in (disabled if unset) |
ADMIN_API_KEY | Admin API key for operators (16+ characters, X-Admin-Key header) |
PLATFORM_TENANT_ID | Platform tenant ID for developer accounts |
Caution
.env.examplelists the storage settings underMINIO_*names, but the names the server actually reads are theSTORAGE_*ones in the table above. UseSTORAGE_*to enable file features.
Caution Never commit secrets such as keys and passwords to the repository; manage them only through environment variables or Docker secrets. In addition, set
MFA_MASTER_KEYfor encrypting MFA secrets (recommended) andCREDENTIALS_ENCRYPTION_KEY(32-byte base64) for encrypting BYO OAuth credentials.
Version History
Version Flow Summary
There is no official version number yet (package version 0.0.0). Below are the development milestones by date.
| Date | Milestone |
|---|---|
| 2026-07-28 | Comprehensive run and test guide; documented what is and isn’t implemented |
| 2026-07-16 | Integration testing against real DB and Redis containers |
| 2026-07-15 | Developer console navigation screens completed (dashboard, data, webhooks, OAuth, usage) |
| 2026-07-14 | Webhooks and connectors, plan limits, usage metering, BYO OAuth, log management |
| 2026-07-13 | Developer console and projects, example app Nexa Words, unique constraints |
| 2026-07-12 | Data API, file storage, reminders, offline sync, SDK |
| 2026-07-11 | Sign-in and tokens, MFA, Google sign-in, users, groups, RBAC |
| 2026-07-10 | Email-verified sign-up |
| 2026-06-23 | Project started (monorepo and server skeleton) |
2026-07-28 — Documentation
Added
- Comprehensive run and test guide by setup level
- List of what is and isn’t implemented, per feature
2026-07-16 — Integration Tests
Added
- Integration tests and CI jobs that verify migrations, tenant isolation, transactions, and queue round-trips against real PostgreSQL and Redis containers
2026-07-15 — Console Navigation
Added
- Console sidebar, project switching, and feature catalog dashboard
- Data playground (collection and record CRUD)
- Webhook registration, delivery history, and resend; BYO OAuth credentials screen
- Usage dashboard (7, 30, 90 days)
Improved
- Responsive layout and accessibility cleanup
2026-07-14 — Operational Features
Added
- Webhook outbox publishing, HMAC-signed delivery and retries, file/reminder/sync events, delivery history and resend
- Slack, Telegram, and KakaoWork connectors
- Automatic blocking at plan limits (user count, storage)
- Daily usage aggregation and console view
- Per-project Google OAuth credentials (stored encrypted)
- Per-category audit log settings and queries
- SDK file and reminder helpers, automatic multipart switching for large files
Fixed
- Off-by-one-day dates in usage queries
2026-07-13 — Developer Console
Added
- Developer accounts (platform tenant) and project creation, list, and details
- Admin API access with console developer tokens; admin-triggered password reset email
- Console web UI (sign-in, projects, users, logs)
- Example app Nexa Words (offline-first vocabulary app)
- Collection unique constraints (
uniqueBy)
Fixed
- SDK defects in browser environments
2026-07-12 — Data, Files, Sync, SDK
Added
- Data API (declarative access rules, queries, RBAC integration)
- File storage (presigned uploads, resumable multipart, list, delete)
- Reminders (device registration, scheduling, delivery worker, recovery)
- Offline sync (delta pull, batch push, 3 conflict policies, tombstone cleanup)
- Client SDK (
@nexa/sdk) with IndexedDB and SQLite persistent stores - Anonymization of deleted accounts after a 30-day grace period
2026-07-11 — Authentication Track Completed
Added
- Sign-in, token issuance and refresh (rotation and reuse detection), sign-out
- Password reset and change, admin approval
- Google social sign-up, linking, and sign-in
/mesettings and MFA (TOTP)- Admin user management, groups and memberships, RBAC
Changed
- All timestamps unified to UTC; dedicated DB schema introduced
2026-07-10 — Sign-Up
Added
- Email-first verified sign-up flow, password strength and breach checks, 3 approval modes
2026-06-23 — Project Start
Added
- pnpm + Turborepo monorepo, NestJS server skeleton, Docker Compose development infrastructure
- Common response format, error codes, logging, DB connection and migrations (06-23 to 06-26)
Known Limitations
- Not publicly released — there are no downloads, hosted service, or npm package, and the repository is private.
- No per-project API keys — clients call only with the project ID and user tokens. Project deletion/transfer and team member invitations are not available yet either.
- No sign-up screen in the console — developer accounts must be created through the sign-up API.
- SDK — does not support MFA sign-in or a sign-up method.
- Files and Reminders screens in the console — these are app-user-only features and cannot be tested from the console.
- Social sign-in supports Google only.
- Billing and subscription charging not implemented — plan limits block only on user count and storage capacity.
- No real-time features — sync is periodic or manual; there are no WebSocket real-time subscriptions or GraphQL.
- Webhooks are outbound only (no receiving webhooks from external services; secret rotation is planned for later).
- Rate limits are counted per server process, so running multiple servers effectively raises the limit.
/health/readychecks only the DB, not Redis or storage.- Unless
MAIL_PROVIDERis set toresend, emails are not actually sent even in production mode. - Databases other than PostgreSQL do not work yet.
- End-to-end verification requiring real credentials (FCM push, real webhook delivery, per-project Google consent screen) and legal review of the terms and privacy policy are still pending.