This content originally appeared on DEV Community and was authored by Tochukwu Nwosa
The Problem I Was About to Create
While building my NestJS backend, I almost made a rookie mistake: creating one big PATCH /users/:id endpoint that could update everything about a user—profile info, password, email, account status, role... you name it.
On the surface, it seemed DRY (Don't Repeat Yourself). One endpoint, one DTO, one service method. Easy, right?
Wrong. Very wrong.
Two Principles That Changed My Mind
After some research and guidance, I learned about two critical principles:
- Single Responsibility Principle (SRP)
- Principle of Least Privilege
Let me break down why they matter and how they apply to API design.
Single Responsibility Principle (SRP)
The idea: Each class (or in this case, endpoint) should have ONE reason to change.
When you have a single endpoint handling profile updates, password changes, email verification, AND admin operations, you're violating SRP because:
- Changing password logic requires touching the same code as profile updates
- Adding email verification affects the password change flow
- Admin status updates are mixed with user self-service operations
This creates a maintenance nightmare as your app grows.
The Better Approach
// ❌ BAD: One endpoint does everything
@Patch(':id')
updateUser(@Param('id') id: string, @Body() updateDto: UpdateUserDto) {
// Handles: name, email, password, role, isActive, etc.
// Who can do what? Unclear.
// What requires verification? Unclear.
}
// ✅ GOOD: Separate endpoints for separate concerns
@Patch(':id') // Profile updates only
updateUserProfile(@Param('id') id: string, @Body() dto: UpdateUserDto) {}
@Post('auth/change-password') // Password change (needs old password)
changePassword(@Body() dto: ChangePasswordDto) {}
@Post('auth/change-email') // Email change (needs verification)
changeEmail(@Body() dto: ChangeEmailDto) {}
@Patch(':id/status') // Admin-only operations
@UseGuards(AdminGuard)
updateUserStatus(@Param('id') id: string, @Body() dto: UpdateUserStatusDto) {}
Each endpoint has ONE job. One reason to exist. One reason to change.
Principle of Least Privilege
The idea: Give the minimum permissions necessary to accomplish a task.
With one big update endpoint, you face security issues:
- Regular users could potentially change their
roleorisActivestatus - Password changes might not require verification
- Email changes might bypass verification flows
How It Works in Practice
// User profile updates - any authenticated user
@Patch(':id')
@UseGuards(AuthGuard)
updateProfile(@User() currentUser, @Param('id') id: string, @Body() dto: UpdateUserDto) {
// Can only update: businessName, whatsappNumber
// Cannot touch: email, password, role, isActive
}
// Password changes - requires OLD password
@Post('auth/change-password')
@UseGuards(AuthGuard)
changePassword(@User() currentUser, @Body() dto: ChangePasswordDto) {
// Must provide oldPassword for verification
// Cannot be changed by admins (security!)
}
// Admin status updates - admin-only
@Patch(':id/status')
@UseGuards(AuthGuard, AdminGuard)
updateStatus(@Param('id') id: string, @Body() dto: UpdateUserStatusDto) {
// Can only update: isActive, role
// Cannot touch: user's personal info
}
Each endpoint has the minimum permissions it needs. Nothing more.
DTOs Match the Operations
In NestJS, your DTOs should reflect these separate concerns:
// UpdateUserDto - profile info only
export class UpdateUserDto {
@IsOptional()
@IsString()
businessName?: string;
@IsOptional()
@IsString()
whatsappNumber?: string;
}
// ChangePasswordDto - requires verification
export class ChangePasswordDto {
@IsString()
@MinLength(8)
oldPassword: string;
@IsString()
@MinLength(8)
newPassword: string;
}
// UpdateUserStatusDto - admin operations
export class UpdateUserStatusDto {
@IsOptional()
@IsBoolean()
isActive?: boolean;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
Notice how each DTO has only the fields relevant to its operation. No mixing.
Real-World Benefits
After restructuring my endpoints this way:
- Security improved - Users can't accidentally (or maliciously) change their role
- Code is clearer - I know exactly what each endpoint does
- Testing is easier - Each endpoint has one specific behavior to test
- Maintenance is simpler - Changing password logic doesn't affect profile updates
Key Takeaways
- One endpoint = one responsibility - Don't mix profile updates with password changes
- Least privilege = better security - Give each operation the minimum permissions needed
- Separate DTOs for separate concerns - Your validation should match your security model
-
Guards enforce boundaries - Use
@UseGuards()to protect sensitive operations
If you're building a NestJS API, think carefully about your endpoint design. That "convenient" single update endpoint might cause problems down the road.
What I'm Building
I'm documenting my journey from frontend to full-stack while building Tech Linkup, a tech events platform for Nigerian cities. Follow along as I learn NestJS, TypeScript, and backend best practices.
Connect with me:
- Portfolio: tochukwu-nwosa.vercel.app
- GitHub: @tochukwunwosa
- Twitter: @tochukwudev
- LinkedIn: nwosa-tochukwu
What principles have you learned while building your backend? Drop them in the comments! 👇
This content originally appeared on DEV Community and was authored by Tochukwu Nwosa
Tochukwu Nwosa | Sciencx (2025-11-26T16:19:22+00:00) Why Your User Update Endpoint Shouldn’t Do Everything: SRP and Least Privilege in NestJS. Retrieved from https://www.scien.cx/2025/11/26/why-your-user-update-endpoint-shouldnt-do-everything-srp-and-least-privilege-in-nestjs/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.