In any complex application, the flow of events can quickly become a tangled mess. A user is created, an invoice is generated, data is synced from a third-party service. When things go wrong—or when an auditor comes knocking—the most critical question is often the hardest to answer: who did what?
Was that Delete operation triggered by an administrator, a customer exercising their right to be forgotten, or an automated cleanup script? Without a clear, consistent way to attribute actions, you're left wading through unstructured logs, guessing at intent, and struggling to maintain security and compliance.
This is where the concept of "Actions as Code" becomes transformative. With Verbs.do, you don't just execute operations; you model them as explicit Verbs that capture the entire context of an event. A crucial part of that context is the actor—the entity performing the action.
The first step to clarity is recognizing that a "user" isn't the only actor in your system. Modern applications are ecosystems of interacting agents. To build a truly robust audit trail, you must treat all of them as first-class citizens:
By failing to distinguish these actors, you lose invaluable context. The action might be UpdateSubscription, but the story is incomplete without knowing if it was performed by the customer, by a support agent on their behalf, or by the billing system automatically.
The Verbs.do API is built to solve this problem elegantly. When you define and execute a Verb, you provide a clear separation between the actor performing the action and the subject being acted upon.
Let's imagine we're building a system where a user can update their profile. First, we define the Verb itself. Note that the subject here defines the type of entity that this Verb acts upon.
import { Verb } from 'verbs.do';
const updateProfile = new Verb({
name: 'UpdateProfile',
description: "A user's profile information is updated.",
// The Verb acts upon a 'UserProfile' entity
subject: { type: 'UserProfile' },
properties: {
displayName: { type: 'string' },
bio: { type: 'string' }
}
});
Now, let's see how we can execute this Verb with different actors, creating a rich, unambiguous event log.
This is the most common scenario. A logged-in user changes their name through your app's UI. When your backend handles this request, you execute the Verb, specifying the user as the actor.
// Executed in your API controller
await updateProfile.execute({
// WHO did it?
actor: { type: 'User', id: 'user_12345' },
// WHAT was it done to?
subject: { id: 'profile_for_user_12345' },
properties: {
displayName: 'Jane Doe'
}
});
Resulting Log: [Timestamp] Actor[User:user_12345] performed Verb[UpdateProfile] on Subject[UserProfile:profile_for_user_12345]
Imagine your system has a feature that syncs employee data from an internal HR system every night. When it updates a user's profile, it's not the user performing the action; it's your sync service.
// Executed within your nightly sync job
await updateProfile.execute({
// WHO did it?
actor: { type: 'System', id: 'HRDataSyncService' },
// WHAT was it done to?
subject: { id: 'profile_for_user_9876' },
properties: {
displayName: 'John Smith (Verified)'
}
});
Resulting Log: [Timestamp] Actor[System:HRDataSyncService] performed Verb[UpdateProfile] on Subject[UserProfile:profile_for_user_9876]
Let's say you use a third-party service like Gravatar to pull in profile pictures. Your application receives a webhook notifying you that a user's avatar has changed. You can model this external signal as an actor.
// Executed in your webhook handler
app.post('/webhooks/gravatar', async (req, res) => {
const { email, new_avatar_url } = req.body;
const user = await findUserByEmail(email);
await updateUserAvatar.execute({
// WHO did it?
actor: { type: 'ThirdParty', id: 'GravatarWebhook' },
// WHAT was it done to?
subject: { id: user.profileId },
properties: {
avatarUrl: new_avatar_url
}
});
res.sendStatus(200);
});
Resulting Log: [Timestamp] Actor[ThirdParty:GravatarWebhook] performed Verb[UpdateUserAvatar] on Subject[UserProfile:profile_for_user_abcdef]
By consistently defining the actor, you transform your system's event stream from a simple list of events into a rich narrative. This unlocks several powerful advantages:
Unbreakable Audit Trails: You now have an immutable, verifiable log that precisely answers "who did what, to what, and when?" for every single action. This is the gold standard for security analysis and compliance with regulations like SOC 2, GDPR, and HIPAA.
Granular Permissioning: With explicit actors, you can build powerful and intuitive access control rules. For example: "Only an actor of type Admin can execute the DeleteAccount Verb," or "The BillingService actor can execute CreateInvoice but not UpdateProfile."
Radically Faster Debugging: When a production issue occurs, your first question is answered instantly. Filter your Verbs.do log by the affected subject and look at the actor of the last action. Was it a user with invalid input? A bug in an internal service? A malformed payload from a webhook? The answer is right there, cutting down investigation time from hours to seconds.
True Business-as-Code: This approach elevates your code to a higher level of abstraction. You are no longer just calling functions; you are modeling the real-world actors and processes of your business, creating a high-fidelity digital twin of your entire operation.
Stop letting the "who" in your system be an ambiguous mystery. By modeling every user, service, and agent as a distinct actor, you build a foundation of clarity, control, and trust.
Ready to bring a new level of clarity to your system's operations? Define, execute, and log your first Verb at Verbs.do.