Skip to main content

Command Palette

Search for a command to run...

REST APIs for Beginners: What I Learned While Building a Spring Boot Application

A practical beginner-friendly guide to REST API design, DTOs, JWT authentication, Spring Security, frontend integration, and the lessons I learned while building a real full-stack application.

Updated
19 min readView as Markdown
REST APIs for Beginners: What I Learned While Building a Spring Boot Application
K
I build things, break things, debug things, and somehow turn them into working projects. Mostly exploring Java, Spring Boot, React, APIs, Git, and whatever interesting tech rabbit hole I fall into next.

REST APIs for Beginners: What I Learned While Building a Spring Boot Application

When I first started learning REST APIs, the idea seemed simple.

GET retrieves data.
POST creates data.
PUT updates data.
PATCH changes part of a resource.
DELETE removes data.

I understood those definitions.

But once I started building a real full-stack application, I realized that knowing HTTP methods is only the beginning.

A real REST API also involves:

  • API design

  • Data validation

  • DTOs

  • Authentication

  • Authorization

  • Database relationships

  • Error handling

  • Security

  • Frontend integration

  • Environment configuration

  • Deployment

I understood REST much better while building my Career Tracker, a full-stack application built with Spring Boot, React, Spring Security, JWT, JPA, and MySQL.

This article is not intended to be a complete specification of REST.

Instead, it is a practical introduction based on the things I learned while building an actual application.

If you're learning REST APIs from scratch, I hope this gives you a clearer picture of how the pieces fit together.


The Project Behind This Article

The project is called Career Tracker.

It is designed to help users organize their job-search journey.

Users can track:

  • Job applications

  • Application status

  • Interviews

  • Upcoming interview information

  • Notifications

  • Dashboard statistics

  • Career analytics

  • Profile information

  • Career progress

The backend is built with:

Java
Spring Boot
Spring Security
JWT
Spring Data JPA
Hibernate
MySQL
Maven

The frontend is built separately using React.

You can explore the source code while reading this article.

Backend Repository

https://github.com/Dev-Venom/career-tracker-api

Frontend Repository

https://github.com/Dev-Venom/career-tracker-ui

The idea behind sharing the repository is simple:

You do not have to only read about the concepts.

You can also open the actual code and see where controllers, services, repositories, DTOs, authentication filters, and entities are implemented.


What Is a REST API?

In simple terms, a REST API allows different applications to communicate through HTTP.

In my Career Tracker application, the React frontend does not directly access MySQL.

Instead, the architecture looks like this:

React Frontend
      |
      | HTTP Request
      v
Spring Boot REST API
      |
      v
Service Layer
      |
      v
Repository Layer
      |
      v
MySQL Database

Suppose the user opens their profile.

React sends:

GET /users/me

Spring Boot receives the request.

The backend determines which user is authenticated, retrieves the necessary information, converts it into a response object, and sends JSON back to React.

A simplified response could look like:

{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com",
  "targetRole": "Java Full Stack Developer",
  "experienceLevel": "Entry Level",
  "activelyLooking": true
}

React receives that JSON and displays the profile.

That is the basic idea.

The frontend asks for something.

The backend processes the request.

The backend returns a predictable response.


Understanding HTTP Methods

HTTP methods communicate what we want to do with a resource.

For a job application resource, the API might look like this:

GET    /applications/my
POST   /applications
GET    /applications/{id}
PUT    /applications/{id}
PATCH  /applications/{id}/status
DELETE /applications/{id}

Let's look at them individually.


GET — Retrieve Data

GET is used when we want to read something.

For example:

GET /applications/my

This retrieves the applications belonging to the currently authenticated user.

Another example:

GET /users/me

This retrieves the currently authenticated user's profile.

A GET request should normally not modify the resource.


POST — Create Something

POST is generally used when creating a new resource.

For example:

POST /applications

The request might contain:

{
  "companyName": "Example Company",
  "jobTitle": "Java Developer",
  "location": "Bangalore",
  "status": "APPLIED"
}

The backend validates the request, creates the application, saves it, and returns a response.


PUT — Update a Resource

PUT is commonly used when updating an existing resource.

For example:

PUT /users/me

In Career Tracker, this is used for updating profile information.

A request could contain:

{
  "name": "John Doe",
  "phone": "9876543210",
  "location": "India",
  "targetRole": "Java Full Stack Developer",
  "experienceLevel": "Entry Level",
  "preferredJobType": "Full Time",
  "preferredLocation": "Remote",
  "activelyLooking": true
}

PATCH — Update Only Part of Something

Sometimes we do not want to update the entire resource.

For example, when dragging an application across a Kanban board, maybe only the status changes.

Instead of resending the whole application, we can use:

PATCH /applications/{id}/status

with something like:

{
  "status": "INTERVIEW"
}

That is a good example of a partial update.


DELETE — Remove Something

When a user deletes an application:

DELETE /applications/{id}

The backend removes the resource if the authenticated user has permission to do so.


REST Endpoints Should Represent Resources

One common beginner approach is to create endpoints like this:

/getApplications
/createApplication
/updateApplication
/deleteApplication

These endpoints may still work.

But REST APIs become easier to understand when URLs represent resources instead of actions.

Instead of:

/createApplication

use:

POST /applications

Instead of:

/deleteApplication

use:

DELETE /applications/{id}

The endpoint represents the resource:

/applications

and the HTTP method represents the operation.

That gives the API a much more predictable structure.


A Controller Should Not Do Everything

When building a small tutorial application, it can be tempting to put database access, validation, and business logic directly inside controllers.

That becomes difficult to maintain once the application grows.

Career Tracker follows a layered structure:

Controller
    |
    v
Service
    |
    v
Repository
    |
    v
Database

Each layer has its own responsibility.

Controller Layer

The controller focuses on HTTP.

It handles things such as:

Request mappings
Request bodies
Path variables
Authentication information
HTTP responses

For example:

GET /users/me

is mapped inside the User controller.

But the controller does not need to contain all the logic required to build the profile response.

It calls the service.


Service Layer

The service handles application logic.

For example, it may:

Find the authenticated user
Update profile information
Calculate career statistics
Convert entities into DTOs
Apply application rules

This keeps the controller smaller and easier to understand.


Repository Layer

The repository handles persistence.

Spring Data JPA allows methods such as:

findByEmail(...)

or:

findByUser_Id(...)

or:

findByUser_IdAndStatus(...)

Spring generates the underlying database queries.

This greatly reduces the amount of repetitive database code.


DTOs Changed How I Think About API Responses

One of the most important things I learned while building this project was the value of DTOs.

DTO means:

Data Transfer Object

Imagine this User entity:

User
├── id
├── name
├── email
├── password
├── role
├── phone
├── location
└── targetRole

If I directly return this entity from the API, the response could contain information that should never reach the client.

The biggest example is:

password

Even if the password is hashed using BCrypt, there is no reason to expose the hash.

Instead, the backend creates something like:

UserResponseDto

The flow becomes:

Database
   |
   v
User Entity
   |
   v
Service
   |
   v
UserResponseDto
   |
   v
JSON Response

The response DTO contains only the fields the frontend needs.

That creates a much safer and cleaner API.


DTOs Also Protect the API Contract

DTOs are not only about security.

They also help separate:

Database model

from:

Public API model

Suppose the database structure changes later.

If the frontend depends directly on the entity structure, that database change may immediately affect the API.

Using DTOs gives us another layer of control.

That was one of the moments where REST API development started to feel less like CRUD and more like application design.


Frontend and Backend Need a Clear Contract

I encountered a simple example of this while implementing the Profile feature.

Imagine Spring Boot returns:

{
  "targetRole": "Java Full Stack Developer"
}

But React tries to access:

user.role

The backend works.

The frontend works.

The request may even return:

200 OK

But the UI still displays the wrong value.

Why?

Because the backend says:

targetRole

while the frontend expects:

role

The frontend should instead use:

user.targetRole

That might look like a tiny issue.

But it taught me an important lesson:

An API response is a contract between systems.

Both sides need to agree on:

Field names
Data types
Meanings
Required values
Optional values
Response structure

When that contract becomes inconsistent, integration bugs appear very quickly.


Naming Matters More Than It Seems

Another interesting issue happened with the word:

role

My User entity already had:

role

for authorization.

For example:

USER
ADMIN

But the Career Tracker profile also needed something representing the user's career goal.

For example:

Java Full Stack Developer
Frontend Developer
Backend Developer

It would have been easy to use the same field for both.

That would have been a bad design.

One value controls authorization.

The other describes career information.

The correct model became:

role
→ USER / ADMIN

targetRole
→ Java Full Stack Developer

This separation prevents profile editing from accidentally interfering with security.

Small naming choices can become important architecture decisions later.


Authentication Changes REST API Design

Before authentication, an API may simply look like:

GET /applications

But once multiple users are using the application, a much more important question appears:

Which applications should this user be allowed to access?

Career Tracker uses Spring Security and JWT authentication.

Some endpoints are public.

For example:

POST /users

for registration.

And:

POST /users/login

for login.

But user-specific endpoints require authentication.

Examples include:

GET /users/me

PUT /users/me

GET /applications/my

How JWT Authentication Works

A simplified login flow looks like:

User enters email + password
          |
          v
POST /users/login
          |
          v
Backend finds user
          |
          v
BCrypt verifies password
          |
          v
JWT is generated
          |
          v
Token returned to frontend

The frontend then includes that token when calling protected APIs.

For example:

Authorization: Bearer <token>

The backend processes the request through a JWT filter.

Request
   |
   v
JWT Filter
   |
   v
Validate Token
   |
   v
Spring Security
   |
   v
Authenticated Controller

If the token is valid, Spring Security knows which user made the request.


Why I Prefer /users/me for Profile APIs

Without authentication, a frontend might request:

GET /users/15

But if the user is already authenticated, the backend already has an identity available.

Career Tracker therefore uses:

GET /users/me

for loading the profile.

And:

PUT /users/me

for updating it.

Conceptually:

JWT
 |
 v
Authenticated Email
 |
 v
UserRepository
 |
 v
Current User

The frontend does not need to decide which user ID should be loaded.

The backend identifies the user from authentication.

This makes the profile API simpler and safer.


Authentication Is Not the Same as Authorization

This distinction is important.

Authentication answers:

Who are you?

Authorization answers:

What are you allowed to do?

A valid JWT proves the user is authenticated.

But the backend should still verify that the user is allowed to access a particular application or interview.

For example, imagine:

DELETE /applications/50

The backend should not delete application 50 only because the requester has a valid token.

It should also verify that application 50 belongs to that user.

This is where APIs move beyond simple CRUD.


Never Let Profile Data Control Security Roles

Another security-related lesson came from profile editing.

A user might be allowed to update:

name
phone
location
targetRole
preferredLocation
activelyLooking

But a normal profile endpoint should not allow something like:

{
  "role": "ADMIN"
}

That field affects authorization.

Security-related properties should not be treated like normal profile data.

This is another reason why using dedicated request DTOs is valuable.

The request DTO defines exactly what the client is allowed to update.


Don't Store Counters You Can Calculate Reliably

Career Tracker's profile displays statistics such as:

Applications
Interviews
Offers
Hired

One possible design would be storing:

applicationCount
interviewCount
offerCount
hiredCount

inside the User table.

But that creates duplicate information.

Suppose:

Applications = 20

Then the user deletes one.

The application table now contains:

19

But what happens if the counter is not updated?

The User table could still say:

20

Now the system contains conflicting information.

Instead, Career Tracker calculates statistics from the application records.

Conceptually:

User
 |
 v
Applications
 |
 +--> INTERVIEW
 |
 +--> OFFER
 |
 +--> HIRED
 |
 +--> ACCEPTED
 |
 v
CareerStatsDto

The real application records remain the source of truth.


Validation Makes APIs More Predictable

A backend should not assume the frontend will always send correct data.

For example, registration should not allow:

Empty name
Invalid email
Empty password

Spring supports Jakarta Validation annotations such as:

@NotBlank

and:

@Email

Validation gives the API a clear boundary.

Invalid data can be rejected before it enters deeper layers of the application.

This is useful because the frontend is not the only possible API client.

Someone could also call the API through:

Postman
curl
Mobile app
Another frontend
Another service

The backend needs to protect itself regardless of where the request comes from.


Test APIs Before Connecting the Frontend

This workflow saved me a lot of debugging time.

Before connecting a REST endpoint to React, test it independently first.

For example, before integrating the Profile page, I tested:

GET /users/me

and:

PUT /users/me

using an API testing tool.

That allows me to separate two possibilities.

If the request already fails independently:

Backend problem

If it works independently but fails inside React:

Frontend integration problem

Possible frontend integration issues include:

Wrong endpoint
Missing JWT
Incorrect Axios configuration
Wrong field name
Wrong request payload
CORS
State management

This is a simple habit, but it makes full-stack debugging much easier.


CORS Becomes Important With Separate Frontend and Backend Apps

During local development, the frontend may run on:

http://localhost:5173

while Spring Boot runs on:

http://localhost:8080

Those are different origins.

The browser applies cross-origin security rules.

The backend therefore needs a CORS configuration telling the browser which frontend origins are allowed.

One thing I changed while preparing Career Tracker for production was moving CORS configuration away from permanently hardcoded production URLs.

Instead, production can use:

app.cors.allowed-origins=${CORS_ALLOWED_ORIGINS}

That means the hosting environment decides which frontend is trusted.

Local development can use localhost.

Production can use the deployed frontend domain.

The Java code does not need to change for every environment.


Never Commit Production Secrets

While preparing the backend for deployment, configuration became another important lesson.

A beginner project might start with something like:

spring.datasource.password=myPassword

jwt.secret=mySecret

That becomes dangerous once the repository is pushed publicly.

Instead, production configuration should use environment variables:

spring.datasource.url=${DB_URL}

spring.datasource.username=${DB_USERNAME}

spring.datasource.password=${DB_PASSWORD}

jwt.secret=${JWT_SECRET}

The actual values live inside the hosting environment.

The repository only contains placeholders.

This gives us:

Local Development
       |
       +--> Local database
       +--> Local JWT secret

and:

Production
       |
       +--> Cloud database
       +--> Production JWT secret

Same application.

Different configuration.


Separate Local and Production Configuration

Spring profiles are useful for this.

For example:

application.properties
application-local.properties
application-prod.properties

Local configuration can contain:

Local database configuration
Local JWT settings
Local CORS origins
Development logging

Production configuration can use:

Environment variables
Cloud database
Production JWT secret
Production CORS origin
Reduced SQL logging

This avoids changing application code when moving from your laptop to production.


REST APIs Are More Than CRUD

CRUD is still one of the best places to start learning.

You should understand:

Create
Read
Update
Delete

But once I started building Career Tracker, REST API development expanded into:

HTTP
Controllers
Services
Repositories
DTOs
Validation
Database Relationships
Authentication
Authorization
JWT
Spring Security
CORS
Exception Handling
Frontend Integration
Environment Variables
Deployment

That was the point where the backend started feeling like an actual system rather than a collection of endpoints.


Common Mistakes I Would Avoid

Here are some mistakes I would pay attention to if I were starting again.

Returning entities directly

Use DTOs when exposing API data.

Returning sensitive information

Password hashes and internal security information do not belong in normal responses.

Putting all logic inside controllers

Use services for business logic.

Hardcoding secrets

Use environment variables.

Making every endpoint public

Only expose what actually needs to be accessible anonymously.

Trusting user IDs blindly

Use authentication and verify resource ownership.

Using one field for multiple meanings

Keep security concepts and domain concepts separate.

Ignoring validation

The backend should validate incoming data.

Using inconsistent names

Frontend and backend need the same API contract.

Creating unnecessary action-style endpoints

Prefer resource-oriented URLs and appropriate HTTP methods.

Connecting the frontend too early

Test the backend independently before debugging integration.


A REST API Learning Path I Would Recommend

If you're starting REST APIs today, I would learn them roughly in this order:

HTTP Basics
    |
    v
GET / POST / PUT / DELETE
    |
    v
Simple CRUD API
    |
    v
Database Integration
    |
    v
Spring Data JPA
    |
    v
DTOs
    |
    v
Validation
    |
    v
Exception Handling
    |
    v
Authentication
    |
    v
JWT
    |
    v
Spring Security
    |
    v
Authorization
    |
    v
Frontend Integration
    |
    v
Environment Variables
    |
    v
Deployment

Do not wait until you completely understand everything before building something.

Build a small part.

Test it.

Break it.

Understand why it broke.

Fix it.

Move forward.

That process teaches a lot.


How to Explore the Career Tracker Repository

If you want to connect the ideas in this article to actual code, you can explore the backend here:

Career Tracker Backend

https://github.com/Dev-Venom/career-tracker-api

The main structure looks like:

src/main/java/com/kumara/careertracker/

├── config/
├── controller/
├── dto/
├── entity/
├── enums/
├── repository/
├── service/
└── CareertrackerApplication.java

If you're a beginner, I recommend exploring it in this order.


1. Start With controller/

Look at the API mappings.

You will see how Java methods are connected to HTTP routes.

For example:

GET
POST
PUT
PATCH
DELETE

This is where an incoming HTTP request first enters the application logic.


2. Move to service/

Then follow the method called by the controller.

This shows where business logic lives.

Controller
    |
    v
Service

This is often where you will see:

User lookup
Data validation
DTO conversion
Status logic
Statistics calculations

3. Explore repository/

Next, see how the service interacts with the database.

Service
   |
   v
Repository
   |
   v
MySQL

Spring Data JPA repositories make many common database operations much easier.


4. Look at dto/

This is especially useful if you're trying to understand frontend/backend communication.

DTOs show:

What the API accepts
What the API returns

Compare the DTOs to the entities.

You will see that they do not always contain the same fields.

That difference is intentional.


5. Finally Explore config/

This package contains some of the more advanced parts of the project.

For example:

Spring Security
JWT utilities
JWT filter
CORS configuration

The authenticated request flow is roughly:

React
  |
  v
HTTP Request + JWT
  |
  v
JWT Filter
  |
  v
Spring Security
  |
  v
Controller
  |
  v
Service
  |
  v
Repository
  |
  v
Database

Once you understand that flow, Spring Security becomes much less mysterious.


The Frontend Is Available Too

The React frontend is separate from the Spring Boot backend.

You can explore it here:

Career Tracker Frontend

https://github.com/Dev-Venom/career-tracker-ui

The frontend consumes the REST APIs for:

Authentication
Dashboard
Applications
Kanban workflow
Interviews
Notifications
Analytics
Profile

The complete architecture is:

React
   |
   | REST / JSON
   v
Spring Boot
   |
   | JPA / Hibernate
   v
MySQL

Looking at both repositories together can help if you're trying to understand how modern frontend and backend applications communicate.


What Building This Project Changed for Me

Before building Career Tracker, REST APIs mostly meant this to me:

GET
POST
PUT
DELETE

Now I think about REST APIs differently.

An API is a contract.

The frontend needs predictable responses.

The backend needs clear responsibilities.

The database needs to remain trustworthy.

Authentication needs to correctly identify users.

Authorization needs to protect resources.

Sensitive information should never leave the backend unnecessarily.

Configuration should work safely across different environments.

And the API should remain understandable as the application grows.

I did not learn all of those lessons from reading one definition of REST.

I learned many of them because something broke while I was building.


Final Thoughts

If you're learning REST APIs right now, start small.

Build CRUD.

Then don't stop there.

Add a database.

Add DTOs.

Add validation.

Add authentication.

Protect some endpoints.

Connect a frontend.

Add proper error handling.

Move your secrets into environment variables.

Try deploying it.

You will run into problems.

That is part of the learning process.

When something goes wrong, trace the request:

Frontend
   |
   v
HTTP Request
   |
   v
Security
   |
   v
Controller
   |
   v
Service
   |
   v
Repository
   |
   v
Database

Ask yourself:

At which part of this flow did my expectation stop matching reality?

That question has helped me debug a lot of issues.

I'm still learning and improving Career Tracker, but building a real project has helped me understand REST APIs much better than learning the concepts only in isolation.

If you're currently learning Spring Boot or REST APIs, feel free to explore the project, experiment with it, fork it, and build your own version.

Backend

https://github.com/Dev-Venom/career-tracker-api

Frontend

https://github.com/Dev-Venom/career-tracker-ui

If this article helped you understand REST APIs a little better, I'd love to know what you're currently building.


Build. Break. Learn.

Real projects. Real learning. Real growth.