Introduction

Essentials

Describe your data, control access, and call the API with types.

The Quickstart gave you a running blog API. This guide builds on that project: first define a post, then decide which posts visitors can see, and finally read them from your application.

The snippets below belong inside the generated config unless an import is shown.

Describe the Data Your App Stores

A model gives a resource a name and a set of fields:

cloud.config.ts
post: model({
  title: t.text().required(),
  body: t.textarea(),
  published: t.boolean().default(false),
})

This creates the posts table and the /api/posts REST resource. CloudConfig also adds an id and timestamps.

Use field builders to describe the values your application accepts:

title: t.text().required()
body: t.textarea()
published: t.boolean().default(false)
publishedAt: t.date()
category: t.select(['news', 'guide'])

Add a relation when one resource belongs to another:

author: t.relation('user')

Add an index to a relation your lists filter on:

author: t.relation('user').index()

After changing fields, relations, or indexes, update the database:

pnpm db:generate
pnpm db:migrate

Decide Who Can Read and Change Data

CloudConfig denies an operation unless an access rule allows it. Keeping the rule beside the model makes the policy visible where the data is defined:

post: model(
  {
    title: t.text().required(),
    published: t.boolean().default(false),
  },
  {
    access: {
      list: ({ where }) => where('published', 'eq', true),
      read: ({ where }) => where('published', 'eq', true),
      create: ({ user }) => user != null,
    },
  },
)

A rule can return true, false, or a where(...) condition. The same rule is used by REST requests and the typed client.

This example allows signed-in users to create posts. Updates and deletions remain denied. The authentication guide shows how to restrict private rows to their owner when your application needs that behavior.

Call the API with Types

The client derives its types from your config. It does not need generated TypeScript files:

import { createClient } from '@cloudconfig/core/client'
import type { config } from './cloud.config'

const api = createClient<typeof config>({
  baseUrl: 'http://localhost:3000/api',
})

const posts = await api.post.list({ page: 1, pageSize: 20 })

list() returns pagination information with the rows:

const { data, totalCount, page, pageSize, totalPages } = posts

To load an existing published post, pass its ID:

const post = await api.post.get(1)

What to Build Next

When posts belong to people, add GitHub sign-in so access rules can identify the user. Read that guide when you need user accounts.

When the app is ready to go online, follow Deployment to choose its host. Local development can continue without a cloud account.

For config file conventions and command lookup, use the project reference. You can return to it as needed.

Copyright © 2026