TutorialFollow along and build something end to end. Start here if Bosbec is new to you.

Build an Event Sign-Up App with Bosbec

This guide sets up My Events, an example event sign-up app built entirely with Bosbec Workflows. It includes a page for creating events, a shareable page for guests, and a REST API behind both.

The example is a complete small application with 85 jobs, six API endpoints, and two web pages. Use it to see how a finished workflow works, then open it in Workflow Builder and adapt it to your own needs. If you want the shortest path to a first API response, start with Quickstart: Your first API.


GET STARTED FOR FREE


What the example includes

Part Where it lives
Admin page: create events, see who signed up GET /admin
Sign-up page: what your guests open GET /register?id=...
REST API: events and registrations /api/*
API reference, rendered from the spec GET /docs
Storage: events and participants as Units Two groups in your account

Both pages are served by the workflow itself, from the same HTTP-in channel as the API. There is no separate web host or deployment step.

Three incoming HTTP triggers for /admin, /register and /docs, each wired to a Send API response job, with the /admin one open showing the page's HTML in its response body, content type text/html and status code 200

A page is an HTML body returned by a Send API response job, using the same mechanism as a JSON endpoint with a different content type.

Import the workflow

Open Workflow Builder and navigate to View > Workflow Library.

Under Templates, find and import the template named Bosbec: My Events. The template contains the pages, API endpoints, and setup trigger used in this guide.

If you created your account using the button on this page, you may already have selected this template when launching Workflow Builder and can skip this step.

Set up the workflow

The workflow needs an HTTP-in channel to expose its pages and API. Create one on your account, then open each HTTP trigger in the workflow and select the channel you created.

Open the create_groups trigger and find the Data operations job at the start of it. It contains two Set data operations for the names of the groups that store your events and participants. Change them if you like, or keep the defaults.

Use plain names: letters, digits, spaces and hyphens. The lookup that follows treats the name as a regular expression, so brackets and dots can cause a miss.

Save and activate the workflow after configuring the triggers.

Right-click the create_groups trigger and choose Start trigger. It looks for each group by name, creates it if it does not exist, and stores the two group IDs in your account settings. Every other job reads the IDs from there, so this is the only place groups are configured.

The create_groups trigger with its Data operations job open, showing two Set data operations writing the group names into metadata.event_group_name and metadata.event_participants_group_name, and the trigger's right-click menu with Start trigger highlighted

The Data operations job holds the two group names. The right-click menu is where you run the trigger.

Running the trigger twice is harmless because it reuses groups that already exist. Do not change the group names after creating events, or the workflow will start looking in a new, empty group while your data remains in the old one.

Use the application

Open /admin on your channel and create an event. Copy the link it gives you, open it in another tab, and sign yourself up. Return to the admin page to see your name in the participant list.

For example, if your channel is https://my-events.in.bosbec.io, open https://my-events.in.bosbec.io/admin.

How it works

Each endpoint follows the same pattern: read the input, validate it, access the stored data, and send a response. The workflow uses separate response jobs for validation errors so that each API error has a clear status and message.

The GET /event flow: a route checking for an event id, a Unit pipeline finding units whose event_id metadata matches the query parameter, then JSON pipelines building the response, with a JSON item template mapping the Unit's metadata fields to name, description, start_datetime, nbr_of_spots and participants

GET /api/event end to end. The Unit pipeline finds the event by event_id, and the JSON pipeline's item template is where a Unit's fields become the shape the API returns.

POST /api/register is the most instructive endpoint. Its trigger checks six things in order, each in its own route, and the first failure wins:

Check Failure
Event ID in the query string 400 missing_event_id
Event exists 404 wrong_event_id
Email present and valid 400 missing_or_invalid_email
Not already registered 409 already_registered
Name present 400 missing_name
Spots left 409 event_full

The routes run in this order from top to bottom. Only after the last check does the Unit pipeline write the participant Unit, a Data operations job increment spots_taken, and the final Send API response return 200 success.

The order matters more than it looks. Validate the body before checking capacity, or a guest who forgot to type their name is told the event is full.

Two Unit types

Events and participants are both stored as Units, in the two groups you created.

An event Unit carries event_id, event_name, event_description, event_start_datetime, event_nbr_of_spots and spots_taken. It also sets firstname to the event ID and lastname to the event name, which is what gives the Unit a readable label in lists.

A participant Unit carries email, name and the event_id of the event they signed up for. That event_id is the only link between the two. There is no relation type, just a matching value, and every lookup filters on it.

Reading the API

The full contract is in openapi.yaml, served at /api/openapi.yaml and rendered for reading at /docs. Two conventions worth knowing before you read it:

Successful reads return data directly. Everything else returns { "status": "...", "message": "..." }. Branch on status; it is stable. message is for humans and may be reworded.

Numbers are numbers. nbr_of_spots and spots_taken are integers, not strings.

The same contract in one file, with request bodies, example responses and every status value, is available here:

DOWNLOAD API DOCS

Extend the application

The template is a starting point rather than a production-ready system. Here are some ways to extend it.

Add a field to events

Say you want a location. It touches five places, and three of them are in the POST /api/create trigger, in the order the jobs run:

  1. The request: accept location in the POST /api/create body.
  2. The Parse JSON to resource job: this is where the incoming JSON is read.
  3. The Unit pipeline: add metadata.event_location to the field mapping, following the defaultvalue(...) pattern the other fields use so a missing value becomes an empty string rather than an error.
  4. The JSON pipeline: include it in the response.
  5. The pages: update the form in /admin and the display in /register, each the HTML body of that trigger's Send API response.

Adding a field to participants, such as dietary requirements, follows the same pattern in the POST /api/register trigger: the Parse JSON to resource, the Unit pipeline that writes the participant, and the sign-up form.

Add a registration deadline

Store a registration_closes timestamp on the event, then add a route in the POST /api/register chain that compares it to the current time and returns 409 registration_closed. Put it next to the capacity check. Both checks ask whether the event can still accept anyone.

Other ideas

  • Waiting list: when event_full, store the participant with a waiting flag instead of refusing them.
  • Confirmation email: send a message when registration succeeds. You will need the channel's absolute URL for the link, since there is no browser to supply it. Store it in account settings, the way group IDs are stored.
  • Editing events: there is no PUT. Add one.
  • Authentication: the admin page is open to anyone who knows the URL.

Troubleshooting

Some workflow problems fail silently: the run succeeds, but a wrong value is used later. An expression that cannot resolve renders as its own text, a Set data operation with an empty result leaves the old value in place, and a route with Compare value is regex enabled ignores its compare operator.

For more detail, see When a job succeeded but did the wrong thing, Working with Variables, and Route From Meta Data.


GET STARTED FOR FREE