Skip to main content
POST
/
api
/
taskCategories
/
delete
Delete Categories
curl --request POST \
  --url https://api.example.com/api/taskCategories/delete \
  --header 'Content-Type: application/json' \
  --data '
{
  "categoriesIds": [
    {}
  ]
}
'

Endpoint

POST /api/taskCategories/delete
Deletes one or more task categories from the system. All tasks assigned to the deleted categories are automatically reassigned to "uncategorized" to maintain referential integrity.
This endpoint uses POST method (not DELETE) because it requires a request body with an array of category IDs.
Data Integrity: This operation automatically reassigns all tasks from deleted categories to "uncategorized". No tasks are lost in the deletion process.

Request Body

categoriesIds
array
required
An array of category UUIDs to delete. Must be an array even when deleting a single category.Behavior:
  • Invalid or non-existent IDs are silently ignored
  • Empty array is accepted but performs no deletions
  • All tasks from deleted categories are reassigned to "uncategorized"
Example: ["a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d"]

Response

On successful deletion, this endpoint returns no response body (204 No Content).

Status Codes

  • 204 No Content - Categories successfully deleted (no response body)
  • 400 Bad Request - Invalid request body (not an array)
  • 404 Not Found - None of the provided category IDs exist
  • 500 Internal Server Error - Server-side error during deletion

Examples

curl -X POST http://localhost:3000/api/taskCategories/delete \
  -H "Content-Type: application/json" \
  -d '{
    "categoriesIds": ["a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d"]
  }'

Request Examples

{
  "categoriesIds": ["a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d"]
}

Response Examples

(No response body)

Task Reassignment Behavior

When categories are deleted, the API automatically reassigns all associated tasks:
1

Identify Affected Tasks

The API finds all tasks where categoryId matches any of the deleted category IDs.
2

Reassign to Uncategorized

All identified tasks have their categoryId field set to "uncategorized".
3

Update Timestamps

The updatedAt field is set to the current timestamp for all reassigned tasks.
4

Delete Categories

After tasks are reassigned, the categories are permanently deleted.

Example Flow

Before Deletion:
// Category
{ "_id": "cat-uuid", "name": "Work" }

// Tasks
[
  { "_id": "task-1", "title": "Task 1", "categoryId": "cat-uuid" },
  { "_id": "task-2", "title": "Task 2", "categoryId": "cat-uuid" }
]
After Deletion:
// Category deleted

// Tasks reassigned
[
  { "_id": "task-1", "title": "Task 1", "categoryId": "uncategorized" },
  { "_id": "task-2", "title": "Task 2", "categoryId": "uncategorized" }
]

Usage Notes

POST Method Required: Unlike typical DELETE operations, this endpoint uses POST because it requires a request body. Standard DELETE requests do not typically include bodies.
Array Required: The categoriesIds parameter must always be an array, even when deleting a single category. Passing a single string will result in a 400 error.
Partial Success: If some IDs in the array don’t exist, they are silently ignored. The operation will succeed (204) as long as at least one valid ID is processed.
No Response Body: Successful deletions return 204 with no response body. Don’t try to parse JSON from the response.

Common Use Cases

Delete Single Category with Confirmation

async function deleteCategoryWithConfirmation(categoryId) {
  // First, get category details
  const getResponse = await fetch(
    `http://localhost:3000/api/taskCategories/${categoryId}`
  );
  
  if (!getResponse.ok) {
    console.error('Category not found');
    return false;
  }
  
  const category = await getResponse.json();
  
  // Get tasks count in this category
  const tasksResponse = await fetch('http://localhost:3000/api/tasks');
  const allTasks = await tasksResponse.json();
  const affectedTasks = allTasks.filter(t => t.categoryId === categoryId);
  
  // Confirm deletion
  const confirmed = confirm(
    `Delete category "${category.name}"?\n` +
    `${affectedTasks.length} task(s) will be moved to Uncategorized.`
  );
  
  if (!confirmed) {
    return false;
  }
  
  // Delete category
  const deleteResponse = await fetch(
    'http://localhost:3000/api/taskCategories/delete',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ categoriesIds: [categoryId] })
    }
  );
  
  return deleteResponse.status === 204;
}

Delete Multiple Categories

async function deleteMultipleCategories(categoryIds) {
  const response = await fetch(
    'http://localhost:3000/api/taskCategories/delete',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ categoriesIds: categoryIds })
    }
  );
  
  if (response.status === 204) {
    console.log(`Successfully deleted ${categoryIds.length} categories`);
    return true;
  }
  
  return false;
}

// Usage
await deleteMultipleCategories([
  'uuid-1',
  'uuid-2',
  'uuid-3'
]);

Delete Empty Categories

async function deleteEmptyCategories() {
  // Fetch all categories and tasks
  const [categoriesRes, tasksRes] = await Promise.all([
    fetch('http://localhost:3000/api/taskCategories'),
    fetch('http://localhost:3000/api/tasks')
  ]);
  
  const categories = await categoriesRes.json();
  const tasks = await tasksRes.json();
  
  // Find categories with no tasks
  const emptyCategories = categories.filter(category => {
    return !tasks.some(task => task.categoryId === category._id);
  });
  
  if (emptyCategories.length === 0) {
    console.log('No empty categories found');
    return [];
  }
  
  console.log(`Found ${emptyCategories.length} empty categories`);
  
  // Delete them
  const categoryIds = emptyCategories.map(c => c._id);
  const response = await fetch(
    'http://localhost:3000/api/taskCategories/delete',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ categoriesIds: categoryIds })
    }
  );
  
  if (response.status === 204) {
    console.log('Empty categories deleted');
    return emptyCategories;
  }
  
  return [];
}

Delete Category and Verify Task Reassignment

async function deleteCategoryAndVerify(categoryId) {
  // Get tasks before deletion
  const tasksBeforeRes = await fetch('http://localhost:3000/api/tasks');
  const tasksBefore = await tasksBeforeRes.json();
  const affectedTasksBefore = tasksBefore.filter(t => t.categoryId === categoryId);
  
  console.log(`Tasks in category before deletion: ${affectedTasksBefore.length}`);
  
  // Delete category
  const deleteRes = await fetch(
    'http://localhost:3000/api/taskCategories/delete',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ categoriesIds: [categoryId] })
    }
  );
  
  if (deleteRes.status !== 204) {
    console.error('Failed to delete category');
    return false;
  }
  
  // Verify tasks were reassigned
  const tasksAfterRes = await fetch('http://localhost:3000/api/tasks');
  const tasksAfter = await tasksAfterRes.json();
  
  const reassignedTasks = affectedTasksBefore.map(oldTask => {
    return tasksAfter.find(t => t._id === oldTask._id);
  });
  
  const allReassigned = reassignedTasks.every(
    task => task && task.categoryId === 'uncategorized'
  );
  
  if (allReassigned) {
    console.log(`All ${reassignedTasks.length} tasks reassigned to uncategorized`);
    return true;
  } else {
    console.error('Some tasks were not properly reassigned');
    return false;
  }
}

Integration Examples

React Delete Button

import { useState } from 'react';

function DeleteCategoryButton({ category, onDeleted }) {
  const [deleting, setDeleting] = useState(false);
  
  const handleDelete = async () => {
    if (!confirm(`Delete category "${category.name}"?`)) {
      return;
    }
    
    setDeleting(true);
    
    try {
      const response = await fetch(
        'http://localhost:3000/api/taskCategories/delete',
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ categoriesIds: [category._id] })
        }
      );
      
      if (response.status === 204) {
        onDeleted(category._id);
      } else {
        alert('Failed to delete category');
      }
    } catch (error) {
      console.error('Error:', error);
      alert('Error deleting category');
    } finally {
      setDeleting(false);
    }
  };
  
  return (
    <button onClick={handleDelete} disabled={deleting}>
      {deleting ? 'Deleting...' : 'Delete'}
    </button>
  );
}

Best Practices

Always Confirm: Implement user confirmation before deleting categories, especially if they contain tasks.
Show Task Count: Display how many tasks will be affected before deletion to help users make informed decisions.
Handle Array Format: Always ensure categoriesIds is an array. Use Array.isArray() checks or wrap single values in an array.
Irreversible Operation: Category deletion is permanent. Consider implementing an “archive” feature instead of hard deletion for important data.

Error Handling

async function deleteCategoriesSafely(categoryIds) {
  // Validate input
  if (!Array.isArray(categoryIds)) {
    categoryIds = [categoryIds];
  }
  
  if (categoryIds.length === 0) {
    console.log('No categories to delete');
    return { success: true, message: 'No categories to delete' };
  }
  
  try {
    const response = await fetch(
      'http://localhost:3000/api/taskCategories/delete',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ categoriesIds: categoryIds })
      }
    );
    
    if (response.status === 204) {
      return { 
        success: true, 
        message: `Deleted ${categoryIds.length} categories` 
      };
    }
    
    if (response.status === 400) {
      const error = await response.json();
      return { success: false, error: 'Invalid request', details: error };
    }
    
    if (response.status === 404) {
      return { success: false, error: 'Categories not found' };
    }
    
    if (response.status === 500) {
      const error = await response.json();
      return { success: false, error: 'Server error', details: error };
    }
    
    return { success: false, error: 'Unexpected error' };
    
  } catch (error) {
    return { success: false, error: 'Network error', details: error };
  }
}

Build docs developers (and LLMs) love