You already know how to make a GET request with fetch. But apps don't just read data, they create, update, and delete it too. HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. defines a small vocabulary of verbs (called methods) that tell the server what you want to do with a resource.
GET, read data
GET is the default method and is used to retrieve data. It should be safe and idempotentWhat is idempotent?An operation that produces the same result whether you perform it once or multiple times, making retries safe.: calling GET a thousand times should not change anything on the server.
// No options needed - GET is the default
const response = await fetch('https://api.example.com/users');
const users = await response.json();GET requests should never have a body. If you need to filter or paginate, use query parameters instead.
body property. This is wrong, the HTTP spec says GET requests should not include a body, and many servers and browsers will ignore or reject it. If Copilot gives you fetch(url, { method: 'GET', body: JSON.stringify(filters) }), refactor the filters into query parameters instead.POST, create a resource
POST is used to create something new. Unlike GET, it sends data in a request body. POST is not idempotentWhat is idempotent?An operation that produces the same result whether you perform it once or multiple times, making retries safe., sending the same POST request twice typically creates two separate records.
const newUser = {
name: 'Alice',
email: 'alice@example.com'
};
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUser)
});
const createdUser = await response.json();
console.log('Created with ID:', createdUser.id);PUT vs PATCH, two kinds of update
This is where most people get confused. Both methods update existing data, but they work differently.
PUT replaces the entire resource. If you omit a field, it gets deleted or reset to its default. Think of it like overwriting a file, you hand the server the complete new version.
// PUT: you must send ALL fields
const updatedUser = {
id: 1,
name: 'Alice Smith',
email: 'alice.smith@example.com',
age: 31 // Every field must be present
};
await fetch('https://api.example.com/users/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedUser)
});PATCH updates only what you send. Fields you omit are left untouched. Use this when you only want to change one or two things.
// PATCH: only send what changed
await fetch('https://api.example.com/users/1', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'new.email@example.com' })
});DELETE, remove a resource
DELETE removes a resource. It's idempotentWhat is idempotent?An operation that produces the same result whether you perform it once or multiple times, making retries safe.: deleting something that no longer exists should ideally return a 404 (or still a 200/204 on some APIs), but the end state is the same, the resource is gone.
const response = await fetch('https://api.example.com/users/1', {
method: 'DELETE'
});
if (response.ok) {
console.log('User deleted successfully');
}DELETE requests usually don't need a body or Content-Type header.
Choosing the right method
| Operation | HTTP method | Idempotent? | Sends body? | Example |
|---|---|---|---|---|
| Read data | GET | Yes | No | List all users |
| Create resource | POST | No | Yes | Create a new post |
| Replace resource | PUT | Yes | Yes | Update full user profile |
| Partial update | PATCH | No (usually) | Yes | Change only email |
| Delete resource | DELETE | Yes | Rarely | Remove a comment |
Content-Type and sending form data
When you send data in a request body, the server needs to know how to interpret it. That's the job of the Content-Type header.
// Sending JSON
headers: { 'Content-Type': 'application/json' }
// Sending URL-encoded form data (like a traditional HTML form)
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
// Sending a file upload - do NOT set Content-Type manually
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch('/upload', {
method: 'POST',
body: formData // Browser sets the correct multipart Content-Type with boundary
});Quick reference
| Operation | HTTP method | Idempotent? | Sends body? |
|---|---|---|---|
| Read data | GET | Yes | No |
| Create resource | POST | No | Yes |
| Replace resource | PUT | Yes | Yes |
| Partial update | PATCH | No (usually) | Yes |
| Delete resource | DELETE | Yes | Rarely |