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

# 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:

```text
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:

```text
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:

```http
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:

```json
{
  "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:

```http
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:

```http
GET /applications/my
```

This retrieves the applications belonging to the currently authenticated user.

Another example:

```http
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:

```http
POST /applications
```

The request might contain:

```json
{
  "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:

```http
PUT /users/me
```

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

A request could contain:

```json
{
  "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:

```http
PATCH /applications/{id}/status
```

with something like:

```json
{
  "status": "INTERVIEW"
}
```

That is a good example of a partial update.

* * *

## DELETE — Remove Something

When a user deletes an application:

```http
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:

```text
/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:

```text
/createApplication
```

use:

```http
POST /applications
```

Instead of:

```text
/deleteApplication
```

use:

```http
DELETE /applications/{id}
```

The endpoint represents the resource:

```text
/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:

```text
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:

```text
Request mappings
Request bodies
Path variables
Authentication information
HTTP responses
```

For example:

```text
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:

```text
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:

```java
findByEmail(...)
```

or:

```java
findByUser_Id(...)
```

or:

```java
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:

```text
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:

```text
password
```

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

Instead, the backend creates something like:

```text
UserResponseDto
```

The flow becomes:

```text
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:

```text
Database model
```

from:

```text
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:

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

But React tries to access:

```javascript
user.role
```

The backend works.

The frontend works.

The request may even return:

```text
200 OK
```

But the UI still displays the wrong value.

Why?

Because the backend says:

```text
targetRole
```

while the frontend expects:

```text
role
```

The frontend should instead use:

```javascript
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:

```text
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:

```text
role
```

My User entity already had:

```text
role
```

for authorization.

For example:

```text
USER
ADMIN
```

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

For example:

```text
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:

```text
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:

```http
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:

```http
POST /users
```

for registration.

And:

```http
POST /users/login
```

for login.

But user-specific endpoints require authentication.

Examples include:

```http
GET /users/me

PUT /users/me

GET /applications/my
```

* * *

# How JWT Authentication Works

A simplified login flow looks like:

```text
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:

```http
Authorization: Bearer <token>
```

The backend processes the request through a JWT filter.

```text
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:

```http
GET /users/15
```

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

Career Tracker therefore uses:

```http
GET /users/me
```

for loading the profile.

And:

```http
PUT /users/me
```

for updating it.

Conceptually:

```text
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:

```http
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:

```text
name
phone
location
targetRole
preferredLocation
activelyLooking
```

But a normal profile endpoint should not allow something like:

```json
{
  "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:

```text
Applications
Interviews
Offers
Hired
```

One possible design would be storing:

```text
applicationCount
interviewCount
offerCount
hiredCount
```

inside the User table.

But that creates duplicate information.

Suppose:

```text
Applications = 20
```

Then the user deletes one.

The application table now contains:

```text
19
```

But what happens if the counter is not updated?

The User table could still say:

```text
20
```

Now the system contains conflicting information.

Instead, Career Tracker calculates statistics from the application records.

Conceptually:

```text
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:

```text
Empty name
Invalid email
Empty password
```

Spring supports Jakarta Validation annotations such as:

```java
@NotBlank
```

and:

```java
@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:

```text
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:

```http
GET /users/me
```

and:

```http
PUT /users/me
```

using an API testing tool.

That allows me to separate two possibilities.

If the request already fails independently:

```text
Backend problem
```

If it works independently but fails inside React:

```text
Frontend integration problem
```

Possible frontend integration issues include:

```text
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:

```text
http://localhost:5173
```

while Spring Boot runs on:

```text
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:

```properties
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:

```properties
spring.datasource.password=myPassword

jwt.secret=mySecret
```

That becomes dangerous once the repository is pushed publicly.

Instead, production configuration should use environment variables:

```properties
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:

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

and:

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

Same application.

Different configuration.

* * *

# Separate Local and Production Configuration

Spring profiles are useful for this.

For example:

```text
application.properties
application-local.properties
application-prod.properties
```

Local configuration can contain:

```text
Local database configuration
Local JWT settings
Local CORS origins
Development logging
```

Production configuration can use:

```text
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:

```text
Create
Read
Update
Delete
```

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

```text
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:

```text
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:

```text
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:

```text
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.

```text
Controller
    |
    v
Service
```

This is often where you will see:

```text
User lookup
Data validation
DTO conversion
Status logic
Statistics calculations
```

* * *

## 3\. Explore `repository/`

Next, see how the service interacts with the database.

```text
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:

```text
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:

```text
Spring Security
JWT utilities
JWT filter
CORS configuration
```

The authenticated request flow is roughly:

```text
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:

```text
Authentication
Dashboard
Applications
Kanban workflow
Interviews
Notifications
Analytics
Profile
```

The complete architecture is:

```text
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:

```text
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:

```text
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.
