When I started learning backend development with Go, I thought the main challenge would be learning how to create API endpoints.
Create a route.
Handle a request.
Return some JSON.
Done.
It didn't take long to realize that the endpoint itself is only a small part of the problem.
The more I built, the more questions appeared.
Where should the business logic live? How should errors be handled? What happens when the client sends invalid data? How should the API communicate with a database? How do I test the behavior instead of just checking whether the server responds?
Those questions changed the way I think about backend development.
Starting with a simple HTTP server
One of the most useful things about learning Go for backend development has been starting close to the standard library.
A simple HTTP handler can look like this:
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
There isn't much code here.
But this small example helped me understand the basic flow:
Client
↓
HTTP request
↓
Handler
↓
Application logic
↓
HTTP response
Once that flow made sense, adding more endpoints became easier.
I practiced GET requests, filtering with query parameters, retrieving resources by ID, creating resources with POST, and deleting resources with DELETE.
For example:
GET /users
GET /users?id=1
POST /users
DELETE /users/1
At first, I focused mainly on making each endpoint work.
Then I started paying more attention to what happened around the endpoint.
An API is a contract
One of the biggest things I learned is that an API isn't just a collection of URLs.
It is a contract between the client and the server.
For example, when a client sends:
POST /users
Content-Type: application/json
with:
{
"name": "Alice",
"age": 25
}
the server needs to decide what that request means.
Is the JSON valid?
Are the required fields present?
Is the age acceptable?
Should a user with the same information already exist?
What should the server return if something goes wrong?
This made HTTP status codes more meaningful to me.
200 OK
201 Created
400 Bad Request
404 Not Found
500 Internal Server Error
They're not just numbers to memorize.
They communicate the result of an operation to whoever is consuming the API.
What happens when things go wrong?
The happy path is easy to demonstrate.
A request comes in.
The server processes it.
The server returns data.
Real applications are more complicated.
What happens when the client requests a resource that doesn't exist?
What happens when the JSON body is malformed?
What happens when a required value is missing?
These cases need to be part of the design.
For example, an API shouldn't return a successful response simply because the server didn't crash.
It should return a response that accurately describes what happened.
That sounds obvious, but building small APIs myself made the distinction much clearer.
Moving beyond handlers
As my backend projects became larger, putting everything inside an HTTP handler stopped making sense.
I started learning about separating responsibilities.
A simplified structure looks like this:
Request
↓
Handler
↓
Service
↓
Repository
↓
Database
The handler is concerned with HTTP.
The service contains application logic.
The repository handles data access.
The database stores the actual data.
This separation doesn't automatically make an application good, but it gives each part a clearer responsibility.
I have been applying this approach while working on Niavo, my full-stack work execution platform.
The backend uses Go with a layered structure, PostgreSQL, migrations, JWT authentication, and REST APIs.
Working on a real project made the idea of separation much easier to understand than reading about it in isolation.
Typed data is easier to reason about
Another lesson came from moving away from loosely structured data.
When learning APIs, it can be tempting to represent everything as generic maps.
For example:
user := map[string]any{
"id": 1,
"name": "Alice",
}
This works, but it becomes harder to reason about as the application grows.
Using a struct gives the data a defined shape:
type User struct {
ID int json:"id"
Name string json:"name"
Age int json:"age"
}
Now the application knows what a User is.
The same model can also become useful when working with JSON, database records, validation, and other parts of the application.
That was one of the points where Go's type system started becoming more useful to me.
Testing changed how I build APIs
My background in software testing also affects the way I approach backend development.
I don't only ask:
Does this endpoint return the expected response?
I also ask:
What happens with invalid input?
What happens if the resource doesn't exist?
What happens if the query returns nothing?
What happens if the client sends an unexpected value?
For example, a simple endpoint might have several scenarios:
Valid request → success
Invalid JSON → 400
Missing resource → 404
Unexpected input → 400
Unexpected failure → 500
Thinking about these cases while building the endpoint makes testing much more intentional.
It also helps me catch problems before they become bigger problems elsewhere in the application.
Building APIs made me think about the whole system
The biggest change in my thinking has been moving from:
"How do I create this endpoint?"
to:
"What happens throughout the system when this request arrives?"
That question takes me beyond the handler.
It makes me think about routing, middleware, validation, business logic, persistence, authentication, errors, testing, and the response sent back to the client.
That is where backend development became much more interesting for me.
What I'm still learning
I don't consider myself finished with backend development.
I'm still learning about system design, architecture, database design, authentication, scalability, and how different pieces of a production system fit together.
But building REST APIs in Go has given me a much stronger foundation.
The biggest lesson so far is simple:
Creating an endpoint is easy. Designing what happens around that endpoint is where the real engineering begins.
I'm looking forward to learning more by continuing to build, test, break, and improve the systems I work on.
What has building APIs taught you?
If you're learning backend development too, I'd be interested in hearing what changed your thinking once you moved beyond creating simple endpoints.
Top comments (0)