Update User Profile
Patch displayName, bio, or avatarUrl. Only fields present in the body are updated. Returns the fresh updated profile slice.
PATCH /api/v1/users/profile — 🔑 Bearer
Patches a small slice of the user row — displayName, bio, avatarUrl. Sparse update: only the fields you send are touched. Returns the updated row (not the full profile — call GET /users/profile for the full read).
For avatar handling, prefer the dedicated PATCH /users/avatar (it also cleans up the previous file from object storage). Setting avatarUrl here works but does not delete the old file from storage.
Request
Body — UpdateProfileDto
All fields optional. Send only what you want to change.
| Field | Type | Validation | Notes |
|---|---|---|---|
displayName | string | MaxLength(100) | Free-form display name |
bio | string | MaxLength(500) | Free-form bio |
avatarUrl | string | IsUrl() | CDN URL — prefer PATCH /users/avatar for orphan cleanup |
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<UpdatedProfileDto>
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"displayName": "John Doe",
"bio": "Software engineer.",
"avatarUrl": "https://cdn.bio.re/avatars/abc.jpg",
"username": "johndoe",
"email": "[email protected]",
"locale": "en"
}
}| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Account id |
displayName | string | null | After update |
bio | string | null | After update |
avatarUrl | string | null | After update |
username | string | null | Echoed for convenience (unchanged by this endpoint) |
email | string | Echoed (unchanged) |
locale | string | null | Echoed (unchanged) |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | (DTO validation) | displayName > 100 chars, bio > 500 chars, avatarUrl not a valid URL |
401 | (guard) | Missing / invalid bearer token |
404 | error.user.not_found | Token decoded but user row missing |
Side effects
- Lookup
User; if missing, thrownot_found. - Build sparse
dataobject from defined keys only. - If no fields supplied, return the existing user row unchanged (no DB write).
- Otherwise:
prisma.user.update()with the sparse data, return the fresh selected slice. - Audit log:
[profile] Updated for user {userId}.
Code samples
curl -X PATCH https://api.bio.re/api/v1/users/profile \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"displayName": "John Doe",
"bio": "Software engineer."
}'type UpdateProfileInput = {
displayName?: string;
bio?: string;
avatarUrl?: string;
};
type UpdatedProfile = {
id: string;
displayName: string | null;
bio: string | null;
avatarUrl: string | null;
username: string | null;
email: string;
locale: string | null;
};
async function updateProfile(accessToken: string, input: UpdateProfileInput): Promise<UpdatedProfile> {
const res = await fetch('https://api.bio.re/api/v1/users/profile', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Update failed'), {
code: json?.error?.code,
});
}
return json.data;
}import { useMutation, useQueryClient } from '@tanstack/react-query';
export function useUpdateProfile() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (input: UpdateProfileInput) => {
const res = await fetch('/api/v1/users/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Update failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as UpdatedProfile;
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['users', 'profile'] });
qc.invalidateQueries({ queryKey: ['auth', 'me'] });
},
});
}Try it
Authorization
bearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
curl -X PATCH "https://loading/api/v1/users/profile" \ -H "Content-Type: application/json" \ -d '{}'{
"success": true,
"data": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"displayName": "John Doe",
"bio": "Software engineer.",
"avatarUrl": "https://cdn.bio.re/avatars/abc.jpg",
"username": "johndoe",
"email": "[email protected]",
"locale": "en"
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/user/user.controller.ts | 62–69 (updateProfile) |
| DTO (request) | apps/api-core/src/modules/user/dto/index.ts | 34–43 (UpdateProfileDto) |
| DTO (response) | apps/api-core/src/modules/user/dto/user-client-response.dto.ts | 88–109 (UpdatedProfileDto) |
| Service | apps/api-core/src/modules/user/user.service.ts | 61–80 (updateProfile) |
| Prisma model | packages/prisma/prisma/schema.prisma | User.displayName, User.bio, User.avatarUrl |