# HTTP DELETE Method: Remove Resources

> Learn how the HTTP DELETE method works, when to use it, and best practices for deleting resources in REST APIs.

Source: https://howhttpworks.com/methods/delete
Last reviewed: 2026-10-05

> **TL;DR:** DELETE removes the resource at a URL. Return 204 when it's gone, 200 if you send back a body, or 202 if deletion happens later. It's idempotent: a second DELETE leaves the same state, even if it returns 404. The server decides whether that means erasing data or just hiding it.

## What is DELETE?

DELETE targets whatever the URL identifies. That might be a database row or a file, but it doesn't have to be. The server can archive the data, keep an audit record, or refuse. [RFC 9110 §9.3.5](https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.5) leaves the underlying storage up to the server.

```bash
curl -i -X DELETE 'https://api.example.com/documents/notes'
```

A successful deletion usually looks like this (responses on this page are trimmed examples):

```http
HTTP/1.1 204 No Content
Date: Mon, 05 Oct 2026 12:00:00 GMT

```

A 204 carries no body and no `Content-Length`.

## Idempotency

Delete the same resource twice and you end up in the same place: it's gone. The status codes don't have to match. The first call can return 204 and the second 404, and that's still idempotent, because idempotency is about server state, not the response. This assumes nobody recreated the resource between calls. Logging each attempt is fine too.

## Response Codes

Return 204 when the resource is gone and there's nothing to say, 200 when you return a body describing the result, or 202 when you've queued the deletion but haven't done it yet. For a resource that's already gone, return 404 (which also covers resources the caller may not see) or 204 so retries look like success. Pick one and document it.

If deletion is blocked by dependent data, return 409 Conflict. If the client sent `If-Match` and the version no longer matches, return 412 Precondition Failed.

## Implementation

This Express server keeps documents in memory and returns 404 on a repeated delete:

```javascript
const express = require('express')
const app = express()
const documents = new Map([['notes', { title: 'Notes' }]])
app.delete('/documents/:id', (req, res) => {
  if (!documents.delete(req.params.id)) {
    return res.status(404).json({ error: 'Not found' })
  }
  res.status(204).end()
})
app.listen(3000)
```

Run `curl -i -X DELETE http://localhost:3000/documents/notes` twice and you'll get 204, then 404. It has no authentication, so add the access checks described below before exposing anything private.

If your API's contract says 404 means "already deleted", the client can treat it as success:

```javascript
async function deleteDocument(id) {
  const response = await fetch(`/api/documents/${encodeURIComponent(id)}`, {
    method: 'DELETE'
  })
  if (response.status === 202) return { pending: true }
  if (response.status === 404 || response.status === 204) return { pending: false }
  if (!response.ok) throw new Error(`DELETE failed: ${response.status}`)
  return { pending: false }
}
```

Check the contract before reusing that 404 rule elsewhere: some services return 404 to hide resources you can't access. The helper also never parses a 204 as JSON.

## Handling Dependencies

When a resource has children, decide whether deleting it rejects, cascades, or detaches them. Enforce that in the database, with a constraint or inside the delete's transaction. A count query followed by a separate delete lets another request insert a child in the gap.

For soft deletion in PostgreSQL, add a condition so a retry keeps the original deletion timestamp:

```sql
UPDATE posts
SET deleted_at = CURRENT_TIMESTAMP
WHERE id = $1 AND deleted_at IS NULL;
```

Put the authorization check in the same statement or transaction. For dependencies, a count in application code can't replace a foreign-key constraint, since a concurrent insert can land right after it. Document whether dependencies cause 409, get detached, or are removed with the parent.

## Bulk Delete

Sending a JSON array of IDs in a DELETE body is tempting, but a DELETE body has no defined meaning in HTTP and some servers and proxies reject it. Use a POST to a dedicated endpoint instead:

```bash
curl -i 'https://api.example.com/document-deletions' \
  -H 'Content-Type: application/json' \
  --data '{"ids":["notes","draft"]}'
```

That endpoint is your API's design, not an HTTP standard. Give it its own retry policy and check authorization for each document.

## Getting DELETE right

Only parse JSON when the status has a body; 204 doesn't. A 202 means deletion is still pending. To delete only the version the client inspected, send the strong ETag from GET in `If-Match`, and check it atomically with the delete.

DELETE responses aren't cacheable. A successful DELETE invalidates the cached response for that URL, but not collection pages that list the resource. See [RFC 9111 §4.4](https://www.rfc-editor.org/rfc/rfc9111.html#section-4.4).

## Security Considerations for DELETE Endpoints

Check that the caller may delete this specific resource, not just that a session exists. Otherwise anyone who guesses an ID can delete it. Cookie-authenticated endpoints also need CSRF protection; CORS alone won't provide it.

With soft deletes, keep the original `deletedAt` on retries, as the SQL above does. Hiding a row also isn't the same as erasing personal data from backups and other stores.

## Related

- [Idempotent](https://howhttpworks.com/glossary/idempotent)
- [If-Match](https://howhttpworks.com/headers/if-match)
- [204 No Content](https://howhttpworks.com/status-codes/204)
