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.
| Type | Screens | APIs & data | Targets |
|---|---|---|---|
| Web-only | yes | optional | web |
| API-only | none | yes | web (the HTTP server) |
| App-only | yes | optional | android, ios |
| Full-stack | yes | yes | web, optionally android, ios, several apps |
Web-only
The default. A server-rendered website or web application.
iwc create my-site --yes
cd my-site
iwc dev --openapp.iwc declares the web target (projects without platforms are web-only too):
{ "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:
iwc create notes-api --yes
cd notes-api
rm -r ui layouts components state styles tests/smoke.test.iwcThen 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):
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{
"name": "Notes API",
"iwc": 1,
"description": "A JSON API with no screens."
}{
"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." }]
}{
"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" } }
}{
"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" }
}{
"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" }
}{
"name": "deleteNote",
"method": "DELETE",
"path": "/notes/:id",
"operation": "db.delete",
"resource": "note.schema.iwc",
"security": { "rateLimit": "30/1m" }
}{
"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:
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:
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 checksFor 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:
iwc create my-app --android --ios{
"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" }
}
}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 --releaseCompiled 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:
iwc create shop --web --android --ios --admin--admin adds a web-only, sign-in protected /admin area. A full-stack project typically has:
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.iwcThe 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