Endpoint
PATCH /api/taskCategories/:id
Updates the name of an existing task category. All tasks assigned to this category will automatically reflect the updated name.
Path Parameters
The unique identifier (UUID) of the category to update. Example: a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d
Request Body
The new category name. Validation:
Minimum length: 2 characters
Maximum length: 25 characters
Automatically trimmed (whitespace removed)
Example: "Work Projects", "Personal Tasks"
Response
Returns the updated category object.
The category’s unique identifier (unchanged).
The updated category name.
Status Codes
200 OK - Category successfully updated
400 Bad Request - Validation failed (invalid name)
404 Not Found - No category exists with the provided ID
Examples
cURL
JavaScript Fetch
JavaScript with Error Handling
curl -X PATCH http://localhost:3000/api/taskCategories/a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d \
-H "Content-Type: application/json" \
-d '{"name":"Work Projects"}'
Request Examples
Basic Update
Another Example
With Whitespace (Auto-trimmed)
{
"name" : "Work Projects"
}
Response Examples
200 OK
404 Not Found
400 Bad Request
{
"_id" : "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d" ,
"name" : "Work Projects"
}
Validation Rules
All validation is performed server-side using Zod schemas. Invalid requests will return detailed error messages.
Field Required Type Min Max name ✓ string 2 25
Usage Notes
ID Unchanged : The category’s _id remains the same after updating. All tasks assigned to this category will automatically have the updated category name when you fetch them.
Task References : Updating a category name does NOT require updating individual tasks. Since tasks reference categories by ID, the relationship is maintained automatically.
Whitespace Handling : The API automatically trims whitespace, so " Work " becomes "Work".
Avoid Duplicates : While not enforced by the API, avoid renaming a category to a name that already exists to prevent user confusion.
Common Use Cases
Rename Category
async function renameCategory ( categoryId , newName ) {
const response = await fetch (
`http://localhost:3000/api/taskCategories/ ${ categoryId } ` ,
{
method: 'PATCH' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name: newName })
}
);
if ( response . ok ) {
const updated = await response . json ();
console . log ( `Category renamed to: ${ updated . name } ` );
return updated ;
}
return null ;
}
Update with Duplicate Check
async function updateCategoryWithDuplicateCheck ( categoryId , newName ) {
// First, check if another category with this name exists
const allCategoriesRes = await fetch ( 'http://localhost:3000/api/taskCategories' );
const categories = await allCategoriesRes . json ();
const duplicate = categories . find (
cat => cat . _id !== categoryId &&
cat . name . toLowerCase () === newName . toLowerCase ()
);
if ( duplicate ) {
console . error ( 'A category with this name already exists' );
return null ;
}
// Proceed with update
const response = await fetch (
`http://localhost:3000/api/taskCategories/ ${ categoryId } ` ,
{
method: 'PATCH' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name: newName })
}
);
if ( response . ok ) {
return await response . json ();
}
return null ;
}
Capitalize Category Name
async function capitalizeCategoryName ( categoryId ) {
// Get current category
const getResponse = await fetch (
`http://localhost:3000/api/taskCategories/ ${ categoryId } `
);
if ( ! getResponse . ok ) {
return null ;
}
const category = await getResponse . json ();
// Capitalize first letter of each word
const capitalizedName = category . name
. split ( ' ' )
. map ( word => word . charAt ( 0 ). toUpperCase () + word . slice ( 1 ). toLowerCase ())
. join ( ' ' );
// Update if different
if ( capitalizedName !== category . name ) {
const updateResponse = await fetch (
`http://localhost:3000/api/taskCategories/ ${ categoryId } ` ,
{
method: 'PATCH' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name: capitalizedName })
}
);
if ( updateResponse . ok ) {
return await updateResponse . json ();
}
}
return category ;
}
Bulk Rename Categories
async function renameCategoriesBulk ( updates ) {
// updates = [{ id: 'uuid', name: 'new name' }, ...]
const results = await Promise . all (
updates . map ( async ({ id , name }) => {
const response = await fetch (
`http://localhost:3000/api/taskCategories/ ${ id } ` ,
{
method: 'PATCH' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name })
}
);
return {
id ,
success: response . ok ,
category: response . ok ? await response . json () : null
};
})
);
const successful = results . filter ( r => r . success ). length ;
console . log ( `Updated ${ successful } of ${ updates . length } categories` );
return results ;
}
// Usage
await renameCategoriesBulk ([
{ id: 'uuid-1' , name: 'Work Projects' },
{ id: 'uuid-2' , name: 'Personal Tasks' },
{ id: 'uuid-3' , name: 'Shopping List' }
]);
Integration Examples
import { useState , useEffect } from 'react' ;
function EditCategoryForm ({ categoryId , onUpdated }) {
const [ name , setName ] = useState ( '' );
const [ loading , setLoading ] = useState ( true );
const [ saving , setSaving ] = useState ( false );
const [ error , setError ] = useState ( null );
useEffect (() => {
async function fetchCategory () {
try {
const response = await fetch (
`http://localhost:3000/api/taskCategories/ ${ categoryId } `
);
if ( ! response . ok ) {
throw new Error ( 'Category not found' );
}
const category = await response . json ();
setName ( category . name );
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
}
fetchCategory ();
}, [ categoryId ]);
const handleSubmit = async ( e ) => {
e . preventDefault ();
setSaving ( true );
setError ( null );
try {
const response = await fetch (
`http://localhost:3000/api/taskCategories/ ${ categoryId } ` ,
{
method: 'PATCH' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name })
}
);
if ( response . status === 400 ) {
setError ( 'Invalid category name' );
return ;
}
if ( response . status === 404 ) {
setError ( 'Category not found' );
return ;
}
if ( ! response . ok ) {
throw new Error ( 'Failed to update category' );
}
const updated = await response . json ();
onUpdated ( updated );
} catch ( err ) {
setError ( err . message );
} finally {
setSaving ( false );
}
};
if ( loading ) return < div > Loading... </ div > ;
return (
< form onSubmit = { handleSubmit } >
< input
type = "text"
value = { name }
onChange = { ( e ) => setName ( e . target . value ) }
minLength = { 2 }
maxLength = { 25 }
required
/>
< button type = "submit" disabled = { saving } >
{ saving ? 'Saving...' : 'Update Category' }
</ button >
{ error && < div style = { { color: 'red' } } > { error } </ div > }
</ form >
);
}
Inline Edit Component
function InlineEditCategory ({ category , onUpdate }) {
const [ editing , setEditing ] = useState ( false );
const [ name , setName ] = useState ( category . name );
const handleSave = async () => {
if ( name === category . name ) {
setEditing ( false );
return ;
}
const response = await fetch (
`http://localhost:3000/api/taskCategories/ ${ category . _id } ` ,
{
method: 'PATCH' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name })
}
);
if ( response . ok ) {
const updated = await response . json ();
onUpdate ( updated );
setEditing ( false );
} else {
// Reset on error
setName ( category . name );
alert ( 'Failed to update category' );
}
};
const handleCancel = () => {
setName ( category . name );
setEditing ( false );
};
if ( editing ) {
return (
< div >
< input
value = { name }
onChange = { ( e ) => setName ( e . target . value ) }
onBlur = { handleSave }
onKeyPress = { ( e ) => {
if ( e . key === 'Enter' ) handleSave ();
if ( e . key === 'Escape' ) handleCancel ();
} }
autoFocus
/>
</ div >
);
}
return (
< div onClick = { () => setEditing ( true ) } >
{ category . name }
</ div >
);
}
Validation
Client-Side Validation
function validateCategoryName ( name ) {
if ( ! name || typeof name !== 'string' ) {
return { valid: false , error: 'Name is required and must be a string' };
}
const trimmed = name . trim ();
if ( trimmed . length < 2 ) {
return { valid: false , error: 'Name must be at least 2 characters' };
}
if ( trimmed . length > 25 ) {
return { valid: false , error: 'Name must be at most 25 characters' };
}
return { valid: true , name: trimmed };
}
async function updateCategoryWithValidation ( categoryId , newName ) {
const validation = validateCategoryName ( newName );
if ( ! validation . valid ) {
console . error ( 'Validation error:' , validation . error );
return null ;
}
const response = await fetch (
`http://localhost:3000/api/taskCategories/ ${ categoryId } ` ,
{
method: 'PATCH' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name: validation . name })
}
);
if ( response . ok ) {
return await response . json ();
}
return null ;
}
Best Practices
Check for Duplicates : Before updating, check if another category with the same name exists to maintain unique category names.
Optimistic Updates : Update your UI immediately, then sync with the API. Revert if the API call fails.
Preserve User Input : If validation fails, preserve the user’s input and show an error message rather than silently failing or resetting the form.
Don’t Rename to “uncategorized” : Avoid renaming categories to “uncategorized” as this is a reserved system value.