Learning Logs

What I Learned from Building a Real Project

2026-08-158 min read

Building changes the learning curve

There is a difference between understanding something and actually having to make it work.

I have read about REST APIs, databases, deployment, CORS, environment variables, frontend and backend communication, and a lot of other things before. Most of them made sense when I was reading about them.

Then I started building an actual application.

Suddenly, “the frontend calls the backend” was no longer just a sentence in a tutorial.

The frontend actually had to call the backend.

The backend actually had to find something in the database.

The database actually had to be reachable.

And then something would break.

That is where the interesting part started.

I started realizing that building a project is basically a long chain of small assumptions. When everything works, you don’t notice them. When one assumption is wrong, the whole chain starts falling apart.


The first lesson: localhost hides a lot of things

Everything feels easy when the application is running on your own machine.

You have something like:

React
   |
   v
localhost:8080
   |
   v
PostgreSQL

You know where everything is.

You know which port the backend is using.

You know where the database is.

You have your environment variables.

Then you deploy it.

Suddenly:

Browser
   |
   v
Vercel
   |
   v
Render
   |
   v
Neon PostgreSQL

And now there are many more things that can go wrong.

The backend URL is different.

The database URL is different.

Environment variables need to exist on the server.

CORS suddenly matters.

The database may reject a connection.

A service may take time to start.

Something that worked perfectly on localhost can fail completely in production.

That experience taught me that deployment isn’t just the final button you press after development.

It is another part of development.


The second lesson: client and server are relative

One thing I used to think about too simply was the phrase “client and server.”

I initially associated:

Frontend = client
Backend = server

Which is correct in the context of a browser talking to an API.

But then I started looking at:

Spring Boot → PostgreSQL

Here, Spring Boot is the client making the database request, and PostgreSQL is the server responding to it.

That made the concept much clearer.

It isn’t really about a piece of software permanently being “the client” or “the server.”

It depends on who is requesting a service.

For example:

Browser
   |
   | request
   v
Spring Boot
   |
   | request
   v
PostgreSQL

The browser is the client to Spring Boot.

Spring Boot is the client to PostgreSQL.

That sounds obvious once you see it, but actually seeing it in a project made it stick in my head.


The third lesson: errors are often farther away than they look

One of the most frustrating things about development is when the error appears in one place but the actual problem is somewhere else.

For example, the frontend might show:

Failed to fetch

That doesn’t necessarily mean the frontend is broken.

The backend might be down.

The backend might be returning an error.

CORS might be blocking the request.

The database connection might have failed.

The URL might be wrong.

An environment variable might be missing.

There are several possible layers:

Browser
   |
   v
Frontend
   |
   v
HTTP request
   |
   v
Backend
   |
   v
Service
   |
   v
Database

Now when something fails, I try to ask:

At which layer did the request actually fail?

That question is much more useful than immediately changing random code.


Debugging taught me more than successful runs

There is something slightly annoying about debugging.

When everything works, you learn that it works.

When something breaks for three hours, you remember exactly why it works.

Some of the things I remember best from the project are the problems I didn’t want to deal with.

Database connection errors.

CORS errors.

Incorrect URLs.

Environment variables.

Deployment failures.

Exceptions that were technically correct but not very helpful.

At the time, these were frustrating.

Later, they became the things I could explain properly.


The database is not just “where the data goes”

Before building real applications, it is easy to think about a database like this:

save data
get data
done

Working on a real application made me think more about what actually happens between those two operations.

The application needs to decide:

  • what entities exist
  • how they relate
  • what should be stored
  • what should not be duplicated
  • how records are identified
  • what happens when something doesn’t exist
  • what happens when something goes wrong

For example, a simple relationship like:

Board
  |
  └── Snippets

already raises questions.

Can a snippet exist without a board?

Can a board be deleted while snippets exist?

Should the database enforce the relationship?

What should the API return when the board doesn’t exist?

These questions are much easier to understand when there is an actual application depending on the answers.


I also learned not to solve problems that don’t exist yet

There is a temptation when learning system design to immediately start thinking about:

  • Redis
  • Kafka
  • microservices
  • load balancers
  • Kubernetes
  • distributed systems
  • horizontal scaling

It is fun.

It also makes a simple application unnecessarily complicated.

For a small application, sometimes this:

React
  |
Spring Boot
  |
PostgreSQL

is enough.

You don’t need seven services and three message queues just because you learned what they are.

One of the better lessons I’ve taken from building projects is:

Start with the simplest design that solves the problem.

Then improve it when there is an actual reason to improve it.


The “future scale” trap

I have caught myself doing this while designing projects.

I’ll be thinking about an application that currently has almost no users, and five minutes later I’m wondering how it will handle ten million requests per second.

At that point, I usually have to stop and ask:

Do I actually have this problem?

Usually, the answer is no.

Thinking about scalability is useful.

Designing for imaginary problems before solving the real one isn’t always useful.

I’d rather have:

simple system
+
clear reasoning
+
room to improve

than:

complicated system
+
lots of technologies
+
no clear reason why

Deployment makes you respect configuration

A local .env file can make everything feel magical.

You have:

DATABASE_URL=...
API_URL=...

and everything works.

Then you deploy and realize the server doesn’t magically know about your local environment.

You have to explicitly configure it.

That taught me something simple:

Configuration is part of the application.

The code isn’t the whole system.

The environment it runs in matters too.


One small habit that helped

When something breaks, I now try to write down what I actually know before changing anything.

For example:

Frontend request:
200 OK

Backend:
running

Database:
connected

Problem:
response body is unexpected

or:

Frontend:
request sent

Backend:
request received

Database:
connection failed

This sounds almost too simple, but it prevents me from randomly changing five things at once.

If I change five things and the problem disappears, I still don’t know what fixed it.

If I change one thing and it works, I learned something.


Documentation is also a debugging tool

I used to think documentation was something you write after finishing a project.

Now I think writing things down while building can actually help you build the project.

For example, writing:

React → Spring Boot → PostgreSQL

forces you to think about what each component is responsible for.

Writing down:

Why PostgreSQL?

forces you to explain the decision.

Writing:

What happens when the board doesn't exist?

forces you to think about error handling.

Sometimes explaining a system is how I discover that I don’t fully understand the system yet.


The weird thing about tutorials

Tutorials are useful.

But there is a point where watching another tutorial becomes procrastination disguised as learning.

You can watch someone build:

React + Spring Boot + PostgreSQL

and feel like you understand the entire stack.

Then you try to build something yourself and spend an hour wondering why the request isn’t reaching the backend.

That gap is important.

The tutorial gives you a map.

The project makes you actually walk the road.


What I value more now

After working on actual projects, I care more about understanding the flow than memorizing individual technologies.

For example, instead of just remembering:

Spring Boot is a Java framework.

I want to understand:

HTTP request

Controller

Service

Repository

Database

Response

And then ask:

What happens if the database fails?

What happens if the record doesn’t exist?

What happens if the input is invalid?

What happens if two requests arrive at the same time?

Those questions are much more interesting to me than simply knowing another framework.


I also learned that “working” isn’t the finish line

Getting the application to run is only one milestone.

There are several stages:

It runs

It works

It handles errors

It can be deployed

It can be understood

It can be maintained

A project that works only on my laptop isn’t quite the same thing as a project I can confidently show someone else.

That distinction has changed how I think about personal projects.


My approach going forward

I want to keep the basic loop simple:

Build

Break

Debug

Understand

Document

Improve

Build again

I’m trying to avoid waiting until I “know enough” before building.

There will always be something I don’t know.

The better approach, at least for me, seems to be building something slightly beyond what I currently understand and then filling the gaps as they appear.


A few things I’m trying to remember

Don’t confuse familiarity with understanding

Seeing a concept ten times isn’t the same as implementing it once.

Don’t add technology just to make a project look impressive

If PostgreSQL solves the problem, PostgreSQL is enough.

If a simple REST API solves it, I don’t need microservices.

Read the error before changing the code

Sometimes the error message is actually telling me exactly what went wrong.

I just don’t want to admit it.

Keep track of what I don’t understand

Instead of thinking:

“I should already know this.”

I try to write it down:

Things I need to understand:

- CORS
- connection pooling
- caching
- transactions
- queues

Then I can come back to them.

Build things that are slightly uncomfortable

If I already know exactly how to build something, I’m probably not learning as much from it.


The biggest lesson

The biggest change for me isn’t that I learned another framework or another deployment platform.

It’s that I started seeing software as a system of connected decisions.

A small application can teach you about:

UI

HTTP

API

Business logic

Database

Deployment

Errors

Users

And once one of those pieces breaks, you have to understand how the others depend on it.

That’s something I don’t think I would have understood as well by only reading about it.

So I’m going to keep building.

Some projects will be useful.

Some will probably be abandoned halfway through.

Some will have terrible first versions.

That’s fine.

The point is to keep closing the gap between:

“I know what this is.”

and

“I’ve actually built something with it.”