Skip to main content

Getting Started with Use Cases

What's a Use Case?

A use case is action that a user can perform in the domain.

For exemple: Reopen Ticket, Reply Message, Add Product

Internally, a use case is responsible for controlling the interaction between entities, repositories and other domain components.

From Clean Architecture book: "Use cases orchestrate the flow of data to and from the entities, and direct those entities to use their Critical Business Rules to achieve the goals of the use case."


$ npm install @herbsjs/herbs


This is an example of how to set up a use case for creating a list (entity):


const { usecase, step, Ok, Err } = require('@herbsjs/herbs')
const { TodoList } = require('../entities/todoList')
const dependency = {}

module.exports.createList = injection =>
usecase('Create List', {
// Input/Request metadata and validation
request: { name: String },

// Output/Response metadata
response: TodoList,

// Pre-run setup
setup: ctx => (ctx.di = Object.assign({}, dependency, injection)),

// Authorization with Audit
authorize: async (user) => (user.canCreateList ? Ok() : Err()),

// Step description and function
'Check if the List is valid': step(ctx => {
const list = ctx.list = new TodoList() = Math.floor(Math.random() * 100000) =

if (!list.isValid()) return Err(list.errors)
return Ok() // returning Ok continues to the next step. Err stops the use case execution.

'Save the List': step(async ctx => {
const repo = new ctx.di.ListRepository(injection)
return (ctx.ret = await repo.insert(ctx.list)) // ctx.ret is the Use Case return

Best Pratices for a Use Case

  • Be modeled around business domain
  • Focused on value
  • Keep it simple by telling stories / flows (steps)
  • Be reusable
  • Be testable
  • Have clear acceptance criteria
  • Use Ubiquitous language
  • Avoid implement field validations using use cases. Prefer Entities for that.
  • Enforce that use cases are the only entry point to your Domain


Use cases are likely to be called and audited indirectly by a Glue. But for didactic purposes or advanced scenarios, this is how to run and audit a use case:

  1. Check if the user has authorization to run this use case
/* Authorization */
const hasAccess = await usecase.authorize(user)
if (hasAccess === false) {
throw new ForbiddenError() // Or any other behavior for a unauthorized user
  1. Prepare your request object and call the .run() function.

/* Execution */
const request = { name: 'The best list' }
const response = await

  1. Audit the execution

/* Audit */
type: 'use case',
description: 'Create List',
authorized: true,
steps: (2) [{…}, {…}],
transactionId: '2bbc60d6-7843-4667-8732-341c22bae42e',
elapsedTime: 172811500n,
request: { name: 'The best list' },
return: {Ok: {…}},
user: { canCreateList: true }

  1. Check if the use case execution returned an error or success

/* Response */
if (response.isErr)
throw new ResponseError(null, { invalidArgs: response.err })
// Or any other behavior for error response
return response.ok // response.ok has the returned value