← Back to Skills

Databases on Tawa

Declare databases in `catalog-info.yaml`. The builder provisions connection strings and injects them as environment variables. Your code reads from…

Ruleprovision

About

Declare databases in `catalog-info.yaml`. The builder provisions connection strings and injects them as environment variables. Your code reads from `process.env`. Identical across all environments.

Skill Content

This is the raw markdown that gets installed as a Claude Code rule.

# Databases on Tawa

## The Short Version

Declare databases in `catalog-info.yaml`. The builder provisions connection strings and injects them as environment variables. Your code reads from `process.env`. Identical across all environments.

```yaml
spec:
  databases:
    - type: mongodb   # → MONGODB_URI
    - type: redis     # → REDIS_URL
    - type: neo4j     # → NEO4J_URI
```

## Supported Types

| Type | Env Variable | Notes |
|------|-------------|-------|
| `mongodb` | `MONGODB_URI` | Recommended for most services |
| `redis` | `REDIS_URL` | Use for caching and queues |
| `neo4j` | `NEO4J_URI` (plus `NEO4J_USERNAME` and `NEO4J_PASSWORD`) | Use for graph data |

**Only these three types are provisioned.** Any other type (such as `postgres`, `mariadb`, or `sqlite`) is logged as a warning and skipped — the build still completes successfully. That database simply isn't provisioned, so its env var is never injected.

## Connecting

```typescript
// MongoDB
import mongoose from 'mongoose'
const uri = process.env.MONGODB_URI
if (!uri) throw new Error('MONGODB_URI not configured')
await mongoose.connect(uri)

// Redis
import Redis from 'ioredis'
const url = process.env.REDIS_URL
if (!url) throw new Error('REDIS_URL not configured')
const redis = new Redis(url)

// Neo4j — NEO4J_URI, NEO4J_USERNAME, and NEO4J_PASSWORD are all injected
import neo4j from 'neo4j-driver'
const uri = process.env.NEO4J_URI
if (!uri) throw new Error('NEO4J_URI not configured')
const driver = neo4j.driver(
  uri,
  neo4j.auth.basic(process.env.NEO4J_USERNAME!, process.env.NEO4J_PASSWORD!),
)
```

## Database Naming

MongoDB databases are named `{service}-{environment}` by default:

| Service | Env | Database |
|---------|-----|---------|
| my-api | sandbox | my-api-sandbox |
| my-api | prod | my-api-prod |

Override with a custom name:
```yaml
spec:
  databases:
    - type: mongodb
      name: shared-data   # uses "shared-data" instead of "{service}-{env}"
```

## Local Development

```
# .env.local
MONGODB_URI=mongodb://localhost:27017/my-api-dev
REDIS_URL=redis://localhost:6379/0
NEO4J_URI=bolt://localhost:7687
```

Or use `tawa config pull` to get the sandbox connection strings:
```bash
tawa config pull  # writes to .env.local
```

## Vault (Dynamic Credentials)

MongoDB credentials are rotated automatically via HashiCorp Vault. The Vault Agent sidecar handles credential injection — your code reads `MONGODB_URI` as always, but the underlying credentials rotate without a redeploy.

Redis and Neo4j Vault rotation is pending (Phase 2c/2d).

## Shared Databases (catalog 0.5.0+)

Services can share databases. The owning service declares `sharedWith`, the consumer declares `consumesDatabase`:

```yaml
# Owning service (analytics-api):
spec:
  databases:
    - type: mongodb
      sharedWith: [reporting-service]

# Consuming service (reporting-service):
spec:
  consumesDatabase:
    - from: analytics-api
      type: mongodb   # → MONGODB_URI_ANALYTICS_API env var
```

## Accessing Data in Running Services

Platform databases are not reachable from localhost. Use the `koko` CLI — it proxies all operations through the Koko API with authentication and safety controls.

```bash
# Install once
npm install -g koko-db

# Authenticate (shares tawa login session)
tawa login
```

### Read Data

```bash
koko query products -s my-api -e prod --filter '{"active":true}' --limit 20
koko sample users   -s my-api -e prod --limit 5
koko stats          -s my-api -e prod
koko collections    -s my-api -e prod
```

### Export / Import (EJSON — type-safe)

`koko export` outputs EJSON format: ObjectIds are preserved as `{"$oid":"..."}` and Dates as `{"$date":"..."}`. This means exports can be re-imported without ObjectId type corruption.

```bash
# Export prod → local file
koko export products -s my-api -e prod -o /tmp/products.ndjson

# Import into sandbox (koko import handles EJSON natively)
koko import products -s my-api -e sandbox -i /tmp/products.ndjson --upsert

# Import into local MongoDB (requires --legacy flag for $oid markers)
mongoimport --db my-api-dev --collection products --file /tmp/products.ndjson --legacy
```

> Always use `koko import` over raw `mongoimport` — no flags to remember, types are handled automatically.

### Copy Between Environments

```bash
# Direct server-side copy — fastest, no download
koko copy products --service my-api --from prod --to sandbox
```

See `tawa-docs/conventions/koko-data-access.md` for the full quickstart.

## What NOT to Do

```yaml
# ❌ WRONG: unsupported types — silently skipped (warning, non-fatal)
spec:
  databases:
    - type: postgres   # not provisioned — logged as a warning and skipped
    - type: mariadb    # not provisioned — logged as a warning and skipped

# ❌ WRONG: hardcoding connection strings
MONGODB_URI=mongodb://prod-host:27017/mydb  # in tawa config — let builder provision it

# ✅ CORRECT: declare in catalog, let builder provision
spec:
  databases:
    - type: mongodb
```

Install

Copy the skill content and save it to:

~/.claude/rules/tawa-database-provisioning.md
Download .md

Coming soon via CLI:

tawa chaac install tawa-database-provisioning

Details

Format
Rule
Category
provision
Version
1.0.7300
Tokens
~1,245
Updated
2026-06-24
platformdatabasedatabasesmongodbredisneo4jprovisioningenv-vars