| title | Build Your First App |
|---|---|
| description | Complete hands-on tutorial - Build a task management application in 10 minutes using ObjectStack Protocol. |
In this tutorial, you'll build a complete task management application with:
- ✅ Task object with title, description, status, and assignee
- ✅ Validation rules
- ✅ Status workflow (Todo → In Progress → Done)
- ✅ List and form views
- ✅ Dashboard with statistics
Time to complete: ~10 minutes
Prerequisites: Node.js 18+, basic TypeScript knowledge
Create a new project and install dependencies:
mkdir task-manager
cd task-manager
npm init -y
npm install typescript zod @objectstack/spec
npx tsc --initCreate the directory structure:
mkdir -p src/{objects,views,workflows}Create src/objects/task.object.ts:
import { ObjectSchema, Field } from '@objectstack/spec';
export const Task = ObjectSchema.create({
name: 'task',
label: 'Task',
pluralLabel: 'Tasks',
icon: 'check-square',
description: 'Track work items and their status',
// This field will be used as the record name/title
nameField: 'title',
// Enable features
enable: {
trackHistory: true,
apiEnabled: true,
searchEnabled: true,
},
fields: {
// Text field - the task title
title: Field.text({
label: 'Title',
required: true,
maxLength: 200,
helpText: 'Brief description of the task',
}),
// Long text for detailed description
description: Field.textarea({
label: 'Description',
maxLength: 5000,
rows: 5,
}),
// Select field for status
status: Field.select({
label: 'Status',
required: true,
defaultValue: 'todo',
options: [
{ value: 'todo', label: 'To Do', color: 'gray' },
{ value: 'in_progress', label: 'In Progress', color: 'blue' },
{ value: 'done', label: 'Done', color: 'green' },
],
}),
// Select field for priority
priority: Field.select({
label: 'Priority',
defaultValue: 'medium',
options: [
{ value: 'low', label: 'Low', color: 'gray' },
{ value: 'medium', label: 'Medium', color: 'yellow' },
{ value: 'high', label: 'High', color: 'red' },
],
}),
// Date field for due date
due_date: Field.date({
label: 'Due Date',
}),
// Lookup field for assignee (references user object)
assignee: Field.lookup({
label: 'Assignee',
reference: 'user',
displayField: 'full_name',
}),
// Boolean field for completed status
completed: Field.boolean({
label: 'Completed',
defaultValue: false,
}),
// Formula field - calculates if task is overdue
is_overdue: Field.formula({
label: 'Is Overdue',
returnType: 'boolean',
expression: 'AND(NOT(completed), due_date < TODAY())',
}),
},
});Create src/workflows/task-validation.ts:
import { ValidationRule } from '@objectstack/spec';
export const taskValidations = [
// Validation: Due date must be in the future for new tasks
ValidationRule.create({
name: 'future_due_date',
object: 'task',
active: true,
errorMessage: 'Due date must be in the future',
condition: 'AND(ISNEW(), due_date < TODAY())',
}),
// Validation: Completed tasks must have status = 'done'
ValidationRule.create({
name: 'completed_status_match',
object: 'task',
active: true,
errorMessage: 'Completed tasks must have status "Done"',
condition: 'AND(completed, status != "done")',
}),
];Create src/views/task-list.view.ts:
import { ListView } from '@objectstack/spec';
export const TaskListView = ListView.create({
name: 'all_tasks',
label: 'All Tasks',
object: 'task',
// Default view type
viewType: 'grid',
// Columns to display
columns: [
{ field: 'title', width: 300 },
{ field: 'status', width: 150 },
{ field: 'priority', width: 120 },
{ field: 'assignee', width: 200 },
{ field: 'due_date', width: 150 },
{ field: 'is_overdue', width: 120 },
],
// Default sorting
sorting: [
{ field: 'due_date', direction: 'asc' },
],
// Default filter
filters: {
completed: { eq: false },
},
// Enable features
enableSearch: true,
enableFilters: true,
enableBulkActions: true,
});Create src/views/task-kanban.view.ts:
import { ListView } from '@objectstack/spec';
export const TaskKanbanView = ListView.create({
name: 'task_kanban',
label: 'Task Board',
object: 'task',
viewType: 'kanban',
// Group by status field
kanbanConfig: {
groupByField: 'status',
cardFields: ['title', 'priority', 'assignee', 'due_date'],
// Define the columns
columns: [
{ value: 'todo', label: 'To Do' },
{ value: 'in_progress', label: 'In Progress' },
{ value: 'done', label: 'Done' },
],
},
});Create src/views/task-form.view.ts:
import { FormView } from '@objectstack/spec';
export const TaskFormView = FormView.create({
name: 'task_edit',
label: 'Task Details',
object: 'task',
// Form layout
layout: {
sections: [
{
label: 'Basic Information',
columns: 2,
fields: ['title', 'description'],
},
{
label: 'Scheduling',
columns: 2,
fields: ['status', 'priority', 'due_date', 'assignee'],
},
{
label: 'Completion',
columns: 1,
fields: ['completed', 'is_overdue'],
},
],
},
// Read-only fields (formula fields are auto read-only)
readOnlyFields: ['is_overdue'],
});Create src/index.ts:
import { Manifest } from '@objectstack/spec';
import { Task } from './objects/task.object';
import { taskValidations } from './workflows/task-validation';
import { TaskListView, TaskKanbanView } from './views/task-list.view';
import { TaskFormView } from './views/task-form.view';
export const manifest = Manifest.create({
name: 'task_manager',
version: '1.0.0',
label: 'Task Manager',
description: 'Simple task management application',
// Register all components
objects: [Task],
validations: taskValidations,
views: {
list: [TaskListView, TaskKanbanView],
form: [TaskFormView],
},
});Add to package.json:
{
"scripts": {
"build": "tsc",
"validate": "node -e \"require('./dist/index.js')\""
}
}Build and validate:
npm run build
npm run validate✅ Congratulations! You've built your first ObjectStack application.
- ✅ How to define Objects with various field types
- ✅ How to add validation rules with formulas
- ✅ How to create multiple view types (Grid, Kanban, Form)
- ✅ How to use formula fields for calculations
- ✅ How to enable platform features (history tracking, search)
Make sure your tsconfig.json has:
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node"
}
}All schemas are validated at build time using Zod. Check the error message for which field failed validation.
The complete source code for a similar example is available at: