Skip to content
ONIWC
iwc 0.1.0

Project types

An IWC project is defined by its files, not by a template: a project with screens serves pages, a project with APIs serves an API, and platforms in app.iwc decides which application targets are built. Four common shapes follow. Each was created and checked with the iwc commands shown.

TypeScreensAPIs & dataTargets
Web-onlyyesoptionalweb
API-onlynoneyesweb (the HTTP server)
App-onlyyesoptionalandroid, ios
Full-stackyesyesweb, optionally android, ios, several apps

Web-only

The default. A server-rendered website or web application.

Terminal
iwc create my-site --yes
cd my-site
iwc dev --open

app.iwc declares the web target (projects without platforms are web-only too):

app.iwc
{ "name": "My site", "iwc": 1, "platforms": { "web": {} } }

Add screens in ui/, a layout in layouts/, and data whenever you need it. Tutorials 1–5 build web-only projects.

API-only

A JSON API with no screens — a back end for a mobile app, a partner integration or a separate front end. Create a project and delete what you don't need, or write the files yourself:

Terminal
iwc create notes-api --yes
cd notes-api
rm -r ui layouts components state styles tests/smoke.test.iwc

Then replace app.iwc with the short one below: the starter's app.iwc refers to styles/main.css and not-found.ui.iwc, which no longer exist, and iwc check says so (stylesheet styles/main.css not found).

A complete API-only project (it passes iwc check and its 8 tests):

Project files
api-only/
├── api/
│   ├── notes-create.api.iwc
│   ├── notes-delete.api.iwc
│   ├── notes-get.api.iwc
│   ├── notes-list.api.iwc
│   └── notes-update.api.iwc
├── database/
│   └── note.schema.iwc
├── tests/
│   └── notes.test.iwc
├── app.iwc
└── package.iwc
app.iwc
{
  "name": "Notes API",
  "iwc": 1,
  "description": "A JSON API with no screens."
}
database/note.schema.iwc
{
  "name": "note",
  "fields": {
    "title": { "type": "string", "required": true, "minLength": 2, "maxLength": 120 },
    "body": { "type": "text", "maxLength": 2000 }
  },
  "access": { "read": "public", "create": "public", "update": "public", "delete": "public" },
  "seed": [{ "id": "n-1", "title": "Hello", "body": "The first note." }]
}
api/notes-list.api.iwc
{
  "name": "listNotes",
  "method": "GET",
  "path": "/notes",
  "operation": "db.list",
  "resource": "note.schema.iwc",
  "request": { "query": { "q": { "type": "string", "maxLength": 60 }, "page": { "type": "integer", "min": 1 } } },
  "response": { "type": "list", "fields": { "id": "string", "title": "string", "body": "string" } }
}
api/notes-create.api.iwc
{
  "name": "createNote",
  "method": "POST",
  "path": "/notes",
  "operation": "db.create",
  "resource": "note.schema.iwc",
  "request": {
    "payload": {
      "title": { "type": "string", "required": true, "minLength": 2, "maxLength": 120 },
      "body": { "type": "string", "maxLength": 2000 }
    }
  },
  "response": { "status": 201, "fields": { "id": "string", "title": "string" } },
  "security": { "rateLimit": "30/1m" }
}
api/notes-update.api.iwc
{
  "name": "updateNote",
  "method": "PATCH",
  "path": "/notes/:id",
  "operation": "db.update",
  "resource": "note.schema.iwc",
  "request": {
    "payload": {
      "title": { "type": "string", "minLength": 2, "maxLength": 120 },
      "body": { "type": "string", "maxLength": 2000 }
    }
  },
  "response": { "fields": { "id": "string", "title": "string", "body": "string" } },
  "security": { "rateLimit": "30/1m" }
}
api/notes-delete.api.iwc
{
  "name": "deleteNote",
  "method": "DELETE",
  "path": "/notes/:id",
  "operation": "db.delete",
  "resource": "note.schema.iwc",
  "security": { "rateLimit": "30/1m" }
}
tests/notes.test.iwc
{
  "name": "notes api",
  "cases": [
    { "name": "lists the seed note", "request": { "path": "/api/notes" }, "expect": { "status": 200, "contains": ["Hello"] } },
    { "name": "rejects a missing title", "request": { "method": "POST", "path": "/api/notes", "body": { "body": "no title" } }, "expect": { "status": 422 } },
    { "name": "rejects an undeclared field", "request": { "method": "POST", "path": "/api/notes", "body": { "title": "Hi there", "admin": true } }, "expect": { "status": 422 } },
    { "name": "creates a note", "request": { "method": "POST", "path": "/api/notes", "body": { "title": "Groceries", "body": "Milk, eggs" } }, "expect": { "status": 201, "body": { "title": "Groceries" } }, "save": { "noteId": "id" } },
    { "name": "reads it back", "request": { "path": "/api/notes/{{noteId}}" }, "expect": { "status": 200, "body": { "body": "Milk, eggs" } } },
    { "name": "updates it", "request": { "method": "PATCH", "path": "/api/notes/{{noteId}}", "body": { "title": "Groceries (done)" } }, "expect": { "status": 200, "body": { "title": "Groceries (done)" } } },
    { "name": "deletes it", "request": { "method": "DELETE", "path": "/api/notes/{{noteId}}" }, "expect": { "status": 200 } },
    { "name": "it is gone", "request": { "path": "/api/notes/{{noteId}}" }, "expect": { "status": 404 } }
  ]
}

Run it and call it:

Terminal
iwc test                       # 8 passed, 0 failed
iwc dev --port 3000
curl -s localhost:3000/api/notes
curl -s -X POST localhost:3000/api/notes -H 'content-type: application/json' -H 'X-IWC-Client: native' -d '{"title":"Groceries"}'
iwc make --api docs            # OpenAPI document + interactive explorer in api-docs/

Writes are protected against cross-site request forgery. Browsers get a CSRF token from IWC's pages; a signed-out client that is not a browser (a script, a server, a native app) sends X-IWC-Client: native and no Origin header instead. Signed-in clients use bearer tokens, which need neither.

iwc security reports what a reviewer should see — here, that anyone may create, update and delete notes:

iwc security
LOW notes-create.api.iwc  public-write
    anyone, signed in or not, can create note.schema.iwc records
    fix: require sign-in, a role or a permission; or route the write through an action that checks

For a real API, require sign-in ("security": {"auth": true}) and let clients authenticate with bearer tokens — see authentication and the authentication tutorial.

App-only

Native Android and iOS apps without a website:

Terminal
iwc create my-app --android --ios
app.iwc
{
  "name": "My app",
  "iwc": 1,
  "platforms": {
    "android": { "id": "com.example.myapp", "minSdk": 26, "versionCode": 1 },
    "ios": { "id": "com.example.myapp", "deploymentTarget": "17.0", "build": "1" }
  }
}
Terminal
iwc check                      # also compiles for each native target
iwc dev --platform android     # build, install and launch on an emulator; rebuild on save
iwc dev --platform ios         # the same on an iPhone simulator
iwc build --all --release

Compiled native apps support a focused set of elements in this first phase (containers, text, buttons, links, images, icons, components and client state). Screens that need forms, records or sign-in either run as a server-driven app or are kept web-only with "platforms": ["web"]. See Android & iOS apps.

Full-stack

Screens, APIs, a database and authentication in one project — optionally with native targets and a separate admin area:

Terminal
iwc create shop --web --android --ios --admin

--admin adds a web-only, sign-in protected /admin area. A full-stack project typically has:

text
shop/
├── app.iwc                    theme, auth, platforms, database
├── database/*.schema.iwc      tables with access rules and seeds
├── api/*.api.iwc              endpoints for forms, apps and partners
├── actions/*.action.iwc       business logic (checkout, refunds…)
├── ui/  layouts/  components/ the web and app screens
├── jobs/  events/  workflows/ background work and lifecycles
└── tests/*.test.iwc

The CRUD, admin dashboard and e-commerce tutorials build full-stack applications. When one project needs several distinct applications — a storefront, an admin console and a courier app sharing one database — declare them as multi-app projects.

Source: apps/marketing/content/docs/project-types.md