Build a Simple CRM with Loom
Walk through building a small CRM with three Threads — Customer, Contact, Note — with relationships between them, custom permissions, and a working API. Estimated time: 20 minutes.
What you'll build
- A Customer Thread (companies you sell to)
- A Contact Thread (people who work at customers)
- A Note Thread (free-form notes attached to a customer)
- Permissions that let
Salesusers read/write but onlyAdmindelete - A working
/api/customers,/api/contacts,/api/notesHTTP API
Step 1 — Scaffold
loom new my-crm --module github.com/me/my-crm
cd my-crm
Step 2 — Customer Thread
# threads/customer.yaml
model: Customer
label: Customer
icon: building
description: A company we sell to.
fields:
- name: company_name
type: text
required: true
searchable: true
- name: website
type: url
- name: industry
type: select
default: "other"
options:
- { label: SaaS, value: saas }
- { label: Manufacturing, value: manufacturing }
- { label: Other, value: other }
- name: notes
type: markdown
permissions:
- role: Admin
can: all
- role: Sales
can: [read, create, update]
Step 3 — Contact Thread (linked to Customer)
# threads/contact.yaml
model: Contact
label: Contact
icon: user
description: A person at a customer company.
fields:
- name: customer_id
type: link
target: Customer
required: true
- name: full_name
type: text
required: true
searchable: true
- name: email
type: email
required: true
unique: true
- name: phone
type: phone
- name: title
type: text
relationships:
- type: belongs_to
target: Customer
foreign_key: customer_id
label: Customer
permissions:
- role: Admin
can: all
- role: Sales
can: [read, create, update]
The link field stores the related Customer's UUID; the relationships block describes the cardinality so the generated UI can render a "Customer:" lookup widget.
Step 4 — Note Thread
# threads/note.yaml
model: Note
label: Note
icon: sticky-note
description: A timestamped note attached to a customer.
fields:
- name: customer_id
type: link
target: Customer
required: true
- name: subject
type: text
required: true
- name: body
type: markdown
required: true
list_view:
columns: [subject, customer_id, created_at]
default_sort: created_at
sort_order: desc
page_size: 25
permissions:
- role: Admin
can: all
- role: Sales
can: [read, create, update]
Step 5 — Validate and weave
loom check
loom weave
Each command should report success. loom weave produces 30 files (3 Threads × 10 outputs each).
Step 6 — Bring up the database
export DATABASE_URL='mysql://root:secret@127.0.0.1:3306/my_crm'
loom stitch
# y
loom_migrations audit table created, plus customers, contacts, notes tables.
Step 7 — Serve
export LOOM_SECRET=$(openssl rand -hex 32)
loom serve
You should see startup logs noting the three Threads were mounted under /api/customers, /api/contacts, /api/notes.
Step 8 — Try it
loom serve requires JWT auth. Issue a token from a small Go program — or for quick testing, use --no-auth and grant Public to one of the Threads:
# threads/customer.yaml
permissions:
- role: Public
can: [read] # add temporarily
- role: Admin
can: all
Restart loom serve --no-auth and:
curl http://localhost:8080/api/customers
# []
curl -X POST http://localhost:8080/api/customers \
-H 'Content-Type: application/json' \
-d '{"company_name":"Acme Inc","website":"https://acme.example.com","industry":"saas"}'
# {"id":"<uuid>","company_name":"Acme Inc",...}
Remove the Public permission before deploying.
What's next
- Add a
before_savehook to lowercase contact emails — see Recipes → Use Hooks to Normalize Data Before Save - Add a Stripe webhook to mark Customer
subscription_active— Tutorials → Adding a Stripe Webhook - Deploy this CRM to production — Tutorials → Deploying Loom to Production