Sell First is $27 this week (usually $97). Price goes back up in 5 days.
Stop building in a vacuum. Stop shipping stuff that never goes out to anyone.
Sell First is the framework I use with my clients — and myself — for building anything on the right foundation. It's how you avoid years of mistakes and actually build a real system, not just another side project that goes nowhere, but the foundation of a real SaaS business.
It's how I built Univjobs, my first startup, and scaled it to $2k/month. It's how I built solidbook and scaled it to $3k/month. And it's how I've done $300k product launches.
The key is to sell first, and build the right foundation. Check it out.
It's $27 this week (back to $97 after Saturday). Five minutes in, you'll know the main things you're doing wrong and exactly how to fix them.
Get Sell First — $27
Ensuring Sequelize Hooks Always Get Run

Intro
In "Decoupling Logic with Domain Events [Guide] - Domain-Driven Design w/ TypeScript", we use Sequelize Hooks to decouple business logic, allowing the system to respond to events in a fashion similar to the Observer Pattern.
Sequelize hooks are places we can write callbacks that get invoked at key points in time like afterCreate, afterDestroy, afterUpdate, and more.
import models from "../models"
import { DomainEvents } from "../../../core/domain/events/DomainEvents"
import { UniqueEntityID } from "../../../core/domain/UniqueEntityID"
const dispatchEventsCallback = (model: any, primaryKeyField: string) => {
const aggregateId = new UniqueEntityID(model[primaryKeyField])
DomainEvents.dispatchEventsForAggregate(aggregateId)
}
(async function createHooksForAggregateRoots() {
const { User } = models
User.addHook("afterCreate", (m: any) => dispatchEventsCallback(m, "user_id"))
User.addHook("afterDestroy", (m: any) => dispatchEventsCallback(m, "user_id"))
User.addHook("afterUpdate", (m: any) => dispatchEventsCallback(m, "user_id"))
User.addHook("afterSave", (m: any) => dispatchEventsCallback(m, "user_id"))
User.addHook("afterUpsert", (m: any) => dispatchEventsCallback(m, "user_id"))
})()In Domain-Driven Design, after a transaction completes, we want to execute domain event handlers in order to decide whether we should invoke any follow up commands or not.
import { UserCreated } from "../../users/domain/events/userCreated"
import { IHandle } from "../../../shared/domain/events/IHandle"
import { CreateMember } from "../useCases/members/createMember/CreateMember"
import { DomainEvents } from "../../../shared/domain/events/DomainEvents"
export class AfterUserCreated implements IHandle<UserCreated> {
private createMember: CreateMember
constructor(createMember: CreateMember) {
this.setupSubscriptions()
this.createMember = createMember
}
setupSubscriptions(): void {
// Register to the domain event
DomainEvents.register(this.onUserCreated.bind(this), UserCreated.name) }
private async onUserCreated(event: UserCreated): Promise<void> { const { user } = event
try {
await this.createMember.execute({ userId: user.userId.id.toString() })
console.log(
`[AfterUserCreated]: Successfully executed CreateMember use case AfterUserCreated`
)
} catch (err) {
console.log(
`[AfterUserCreated]: Failed to execute CreateMember use case AfterUserCreated.`
)
}
}
}In the Sequelize Repository, where we deal with persistence logic, I have noticed that the hook callbacks do not get called if no new rows were created and no columns were changed.
In the save method of a repository, you'll find code where we rely on a mapper to convert the domain object to the format necessary for Sequelize to save it. You'll also find code that determines if we're doing a create or an update based on the domain object's existence.
export class SequelizePostRepo implements PostRepo {
...
public async save (post: Post): Promise<void> {
const PostModel = this.models.Post;
const exists = await this.exists(post.postId);
const isNewPost = !exists;
const rawSequelizePost = await PostMap.toPersistence(post);
if (isNewPost) {
try {
await PostModel.create(rawSequelizePost);
await this.saveComments(post.comments);
await this.savePostVotes(post.getVotes());
} catch (err) {
await this.delete(post.postId);
throw new Error(err.toString())
}
} else {
await this.saveComments(post.comments);
await this.savePostVotes(post.getVotes());
// Persist the post model to the database await PostModel.update(rawSequelizePost, { where: { post_id: post.postId.id.toString() } }); }
}
}In the highlighted lines, I expect the afterUpdate hook to get called, though it will not in scenarios where there were no are differences.
To fix this, Sequelize's update method's second parameter configuration object allows you to pass in hooks: true.
await PostModel.update(rawSequelizePost, {
where: { post_id: post.postId.id.toString() }
// Be sure to include this to call hooks regardless
// of anything changed or not.
hooks: true,});Doing this will ensure that the hooks get run everytime.
Sell First is $27 this week (usually $97). Price goes back up in 5 days.
Stop building in a vacuum. Stop shipping stuff that never goes out to anyone.
Sell First is the framework I use with my clients — and myself — for building anything on the right foundation. It's how you avoid years of mistakes and actually build a real system, not just another side project that goes nowhere, but the foundation of a real SaaS business.
It's how I built Univjobs, my first startup, and scaled it to $2k/month. It's how I built solidbook and scaled it to $3k/month. And it's how I've done $300k product launches.
The key is to sell first, and build the right foundation. Check it out.
It's $27 this week (back to $97 after Saturday). Five minutes in, you'll know the main things you're doing wrong and exactly how to fix them.
Get Sell First — $27
Not sure where you are on the path? Take the Phases of Craftship quiz — 7 questions, personalized results.
Stay in touch!
Join 20000+ value-creating Software Essentialists getting actionable advice on how to master what matters each week. 🖖
View more in Sequelize