JavaRush /Courses /Go SELF /HTTP basics — methods, status codes, idempotency

HTTP basics — methods, status codes, idempotency

Go SELF
Level 56, Lesson 0
Available

1. HTTP as a contract: request → response

When people hear “HTTP” for the first time, they often think: “isn’t that about websites?” And yes, websites too. But for a developer, HTTP is first and foremost a very strict way for two programs to agree: one sends a request (client), the other responds (server). This agreement is called a contract, and it includes not only “which URL to hit,” but also “which method,” “which success/error codes,” “what body format,” and “what counts as valid input.”

In Go this is especially pleasant: historically, Go is a very strong language for servers and network code. Even in materials about Go’s evolution, it is emphasized that Go was designed for writing servers.

The “envelope” mental model

Imagine that HTTP is mail.

  • Letter = request.
  • Envelope = metadata (method, path, headers).
  • Letter contents = request body (for example JSON).
  • Reply letter = response.
  • Stamp on the reply = status code (200, 404, 500…), which says what happened—briefly and unambiguously.

It sounds a bit old-school, but as a mental model it works perfectly: the server does not “guess based on mood,” it responds formally.

Request/response diagram

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: HTTP Request (method + path + headers + body)
    S->>S: validate + compute
    S-->>C: HTTP Response (status + headers + body)

The most important idea from this diagram: the output always includes a status code, while the response body may or may not be present. And the client must be able to operate based on the status code, rather than trying to “parse the error text with human eyes.”

Endpoint: method + path

To stop talking about HTTP as magic, it helps to introduce a small term: an endpoint is a specific API “entry point.” It consists of two parts: the HTTP method (what we do) and the path (what we work with).

For example, “get a list of tasks” often looks like GET /api/v1/tasks.

Right now we are not starting a server or writing handlers. We are doing what experienced engineers do at the start of a project: locking in the contract. And for that, we can even write a couple of structs in Go—not because we “need code,” but to make the thinking more precise.

Mini type for describing an endpoint

package main

type Endpoint struct {
	Method string
	Path   string
}

var listTasks = Endpoint{Method: "GET", Path: "/api/v1/tasks"}
var createTask = Endpoint{Method: "POST", Path: "/api/v1/tasks"}

What matters here is not “pretty code,” but disciplined thinking: an endpoint is exactly the pair (method, path). If you change the method, you change the meaning. If you change the path, you change the resource.

2. HTTP methods: action meaning and promises to the client

It helps to think of methods as verbs with very strict rules. If you use methods “however you want,” the client has to guess behavior, and the API turns into a “guess the button” quest. If you stick to standard semantics, the client (and other programmers) understand behavior without reading the server source code. This saves hours, nerves, and the number of messages like “why is this 200 when it’s an error?”

Below is a practical table that people actually keep in mind when designing an API.

Table of basic method semantics

Method Idea (in plain English) Usually about Idempotency (idea)
GET
“Give me data” reading yes
POST
“Create / perform an action” create/command usually no
PUT
“Replace completely” full replacement of a resource yes
PATCH
“Change partially” partial update usually yes (if designed carefully)
DELETE
“Delete” deletion yes

A couple of comments so this does not look like dry theory.

GET is almost always safe in the sense that it must not change state on the server. If GET suddenly creates a task, charges money, and emails your mom—that’s not GET, that’s a horror movie plot.

POST is often used for creation: “create a task,” “create a user.” But sometimes POST is a “command” that does not fit into “replace state.” For example: “start report recomputation.” In our learning task domain, we will try to keep POST as “create a task,” because it’s simpler.

PUT and PATCH: if you are not sure, do not rush to apply them. But understanding the meaning matters: PUT is “here is the new full version of the resource,” PATCH is “here is a small change.”

DELETE is delete. And this is where philosophy begins: what does “delete” mean if the resource is already deleted? Typically, the agreement is designed so that repeating DELETE does not break the system.

3. Status codes: the server’s response language without guessing from text

If the method is “what we want,” then the status code is “what we got.” And this is not a minor detail—it is part of the contract: the client writes its logic based on the codes. In a good API, the client does not need to “read an error string” and figure out what you meant. It sees 404 and understands “not found.” It sees 400 and understands “I sent something wrong.”

There is a very convenient mental model here: code classes.

  • 2xx — success,
  • 4xx — problem with the request (the client is at fault, or at least the client can fix it),
  • 5xx — problem on the server (the client is not at fault).

A tiny “code class” function

package main

func statusClass(code int) int {
	return code / 100
}

// 200 -> 2
// 404 -> 4
// 500 -> 5

Yes, it’s middle-school math. But it helps: you see statusClass == 4—that means “request error.” You see statusClass == 5—“something broke on our side.”

The minimal set of codes you should understand confidently

For our future tasks API, a small “basic five” (plus one) is enough:

Code Name When it is typically used Is there a response body?
200
OK successful read/update usually yes
201
Created resource created usually yes (often the created object is returned)
204
No Content success with no body no (and this matters!)
400
Bad Request invalid input/format/validation usually yes (error details in JSON)
404
Not Found resource not found usually yes
500
Internal Server Error internal server error usually yes (but the message should be stable and safe)

A critical nuance about 204: it is not “200, but without enthusiasm.” It is an explicit signal: “success, no body.” If you return 204 and still send JSON, some clients will simply ignore it, and others will behave unpredictably. In other words, you will create a bug for yourself, and then heroically fix it later.

Quick helper: “is it a success?”

package main

func is2xx(code int) bool {
	return code >= 200 && code <= 299
}

4. Idempotency: repeat the request—the world does not get worse

The word “idempotency” sounds like it was invented to scare students on exams. In practice, the idea is very simple: if you repeat a request, the final effect must not change. That does not mean “nothing changes.” It means “a repeat does not add extra effect.”

A real-life example: a “delete task” button. If the user clicks it twice (or the network hiccups and the client retries), the task should remain deleted, not be “deleted twice” (whatever that would mean).

Idempotency matters because of the real world: networks are unstable, connections drop, the client may not receive a response and decide to retry. If you design the API so that retries are safe, you reduce weird duplicates and “ghost” states.

A rough rule of thumb as a first approximation

A common approach is: assume GET/PUT/PATCH/DELETE should be idempotent, while POST should not (because “create” twice = two resources). This is not an absolute truth, but it is good starting discipline.

package main

func isIdempotent(method string) bool {
	switch method {
	case "GET", "PUT", "PATCH", "DELETE":
		return true
	default:
		return false
	}
}

A small demo to make it “tangible”

package main

import "fmt"

func main() {
	fmt.Println(isIdempotent("GET"))    // true
	fmt.Println(isIdempotent("POST"))   // false
	fmt.Println(isIdempotent("DELETE")) // true
}

This is, of course, not “real API safety.” But it is a good way to stop confusing idempotency with “does not change anything.”

5. Tie it to the domain: a draft HTTP contract for tasks

Now we will take an important step: translate abstract HTTP words into the concrete vocabulary of our application. We have been living in the tasks domain (tasks) for a while: add a task, view the list, mark as done. In an HTTP style, we want the path to describe the entity, and the method to describe the action.

Let’s agree to version the API via the path. It looks like this: /api/v1/.... A version in the path is a way to say: “the contract is locked in, and if we break it, we’ll do so in another version—not suddenly on a Friday evening.”

Endpoint table as a contract draft

Operation meaning Method Path Success
Task list
GET
/api/v1/tasks
200
Create a task
POST
/api/v1/tasks
201
Get a task
GET
/api/v1/tasks/{id}
200
Mark as “done”
PATCH
/api/v1/tasks/{id}/done
200 (or 204, if we decide to return no body)
Delete a task
DELETE
/api/v1/tasks/{id}
204

Note: we are not discussing JSON formats and a unified error format yet (that is also part of the contract, but it is a separate topic). Here we practice the “skeleton”: method, path, success status—so we do not end up with POST /getTask and GET /deleteTask. We write the API so it can be read like English (or like very strange English).

A bit of code to lock in methods and avoid hand-typed strings

Hand-typed strings like "GET" and "POST" work, but it is easy to make a mistake. So even at an early stage, it is useful to define constants.

package main

type Method string

const (
	MethodGet    Method = "GET"
	MethodPost   Method = "POST"
	MethodPut    Method = "PUT"
	MethodPatch  Method = "PATCH"
	MethodDelete Method = "DELETE"
)

And now endpoints can be described in a “typed” way:

package main

type Endpoint struct {
	Method Method
	Path   string
}

var listTasks = Endpoint{Method: MethodGet, Path: "/api/v1/tasks"}
var deleteTask = Endpoint{Method: MethodDelete, Path: "/api/v1/tasks/{id}"}

6. Common mistakes when designing an HTTP API

Mistake No. 1: changing state via GET.
Usually this happens “for convenience”: “I’ll just open it by link…”. But GET is reading. If GET changes state, you break caching, break repeat requests, and surprise every client. At some point you will catch a bug like “our task creates itself”—and it will not be magic, it will be GET.

Mistake No. 2: always responding 200 and hiding errors in text.
Sometimes people do this: status 200, and inside the JSON there is a field "error": "...". For the client this is terrible: it has to parse the body to understand whether it is a success or not. It is better when success is 2xx, request errors are 4xx, and internal server errors are 5xx. The error body can be structured, but the code is primary.

Mistake No. 3: confusing 400 and 404.
If the client sent id = "abc" and you cannot parse it as a number, that is not “not found,” that is “bad request,” i.e. 400. And 404 is when the id is valid, but there is no such resource. This distinction greatly simplifies life for clients and tests.

Mistake No. 4: returning a response body with 204.
204 means “success, but no body.” If you still send JSON, some clients will ignore it. You end up with an API that “sometimes works.” And “sometimes works” is a special kind of pain, because there is nothing to debug: it “sometimes” works.

Mistake No. 5: not thinking about idempotency and retries.
In the real world, requests get retried. If you design operations such that repeating DELETE suddenly turns into 500, or repeating PATCH creates duplicate changes, clients will be afraid of retries and you will keep catching “weird” states. Idempotency is not decoration; it is insurance against network reality.

1
Task
Go SELF, level 56, lesson 0
Locked
Response class
Response class
1
Task
Go SELF, level 56, lesson 0
Locked
Idempotent method
Idempotent method
1
Task
Go SELF, level 56, lesson 0
Locked
Contract draft
Contract draft
1
Task
Go SELF, level 56, lesson 0
Locked
Contract simulator
Contract simulator
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION