# Building a Rest API with Spring Boot in 10 steps

## Step 1: Install Necessary Tools

**1- Install sdkman**

* [https://sdkman.io/install](https://sdkman.io/install)
    

**2- Install java:**

* Run:  sdk install java
    
* Verify:  java -version
    

**3- Install Eclipse STS (Spring Tools for Eclipse):**

* Download from: https://spring.io/tools
    
* For example, in my case, I’ve downloaded: spring-tool-suite-4-4.28.1.RELEASE-e4.34.0-macosx.cocoa.aarch64.dmg
    
* Install and launch STS
    

**4- Install Apache Maven:**

* STS **comes with an embedded Maven**, but it’s recommend to install it separately for better control
    
* Run: brew install maven
    
* Verify Installation, by running: mvn -version
    
* Note: if Maven is not picking up the right version of java you may need to set the **JAVA\_HOME** in your shell config file, for example, in my case (since I’m using MacOS): Run: echo 'export JAVA\_HOME=$HOME/.sdkman/candidates/java/current' &gt;&gt; ~/.zshrc source ~/.zshrc
    
* Run: mvn -version:
    

You should see an output like this:

<table><tbody><tr><td colspan="1" rowspan="1"><p><strong>Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)</strong></p></td></tr></tbody></table>

## Step 2: Create a New Spring Boot Project in STS

1. **Open Spring Tool Suite (STS)**
    
2. Go to **File → New → Spring Starter Project**
    
3. Enter project details:
    
    * **Name:** spring-crud-posts
        
    * **Type:** Maven
        
    * **Packaging:** Jar
        
    * **Java Version:** 17 (or the latest installed)
        
    * **Spring Boot Version:** Select **3.x.x (Latest)**
        
4. Click **Next**.
    

## Step 3: Add additional dependencies to your pom.xml:

```xml
<dependencies>
    <!-- These are in addition to the dependencies included by default -->
    <!-- Spring Web (for REST API) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Data JPA (for database access) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- H2 Database (for testing, can be replaced with MySQL/PostgreSQL) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>
```

Now test, by running the Spring Boot Application

1. Open SpringCrudPostsApplication.java
    
2. **Run it**:
    
    * Right-click → **Run As → Spring Boot App**
        
3. **You should see logs indicating that Tomcat has started on port 8080:**
    

Tomcat started on port 8080

## Step 4: Create a Simple REST API Endpoint and test it:

1- Create a new package: com.example.demo.controller

2- Create a new class with the following code:

```java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping("api/posts")

public class PostController {

    @GetMapping("/hello")
    
    public String sayHello() {
    
        return "Hello World with Spring, so exciting";
    
    }

}
```

Test the new endpoint by invoking [http://localhost:8080/api/posts/hello](http://localhost:8080/api/posts/hello) on your browser:

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXc_1AE5oDJPWZUk0n9Oql0hw1xqiFpYCw2lZRH6Utl1k0oaB-Z9ENOODIDCF2rawHDcjIVZ9wI3yDquiqUSRSagtnOG3NxT5RjxRGDFkfk4LQLmJZji3hkkzRG9SKGhLVc-6mqVNA?key=0w8rH2kOvftLEbAnzKioGVAR align="left")

## Step 5: Configure in Memory Database:

Open the application.properties file and add the following configuration:

```plaintext
# Enable H2 Console

spring.h2.console.enabled=true

spring.h2.console.path=/h2-console

# H2 Database Configuration

spring.datasource.url=jdbc:h2:mem:testdb

spring.datasource.driverClassName=org.h2.Driver

spring.datasource.username=sa

spring.datasource.password=

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
```

Then restart the application and test the connection:

👉 **http://localhost:8080/h2-console**

* **JDBC URL:** jdbc:h2:mem:testdb
    
* **Username:** sa
    
* **Password:** *(leave blank)*
    
* Click **Connect**.
    

## Step 6: Implement a simple entity:

```java
package com.example.demo.model;


import jakarta.persistence.Column;

import jakarta.persistence.Entity;

import jakarta.persistence.GeneratedValue;

import jakarta.persistence.GenerationType;

import jakarta.persistence.Id;

import jakarta.persistence.Table;


@Entity

@Table(name = "posts")

public class Post {

    @Id
    
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    
    private Long id;
    
    @Column(nullable = false)
    
    private String title;
    
    @Column(nullable = false, columnDefinition = "TEXT")
    
    private String content;
    
    public Post() {
    
    }
    
    public Post(String title, String content) {
    
        this.title = title;
    
        this.content = content;
    
    }
    
    
    public String getTitle() {
    
        return title;
    
    }
    
    
    public void setTitle(String title) {
    
        this.title = title;
    
    }
    
    
    public String getContent() {
    
        return content;
    
    }
    
    
    public void setContent(String content) {
        this.content = content;
    }
    
}
```

## Step 7: Implement a Repository:

```java
package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;

import com.example.demo.model.Post;

//This makes it a Spring-managed bean

@Repository

public interface PostRepository extends JpaRepository<Post, Long> {

}
```

## Step 8 - Implement the Service layer:

```java
package com.example.demo.service;

import java.util.List;

import org.springframework.stereotype.Service;

import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.model.Post;
import com.example.demo.repository.PostRepository;

@Service
public class PostService {

	private final PostRepository repository;

	// As long as the class has one constructor Spring injects the dependency
	// without the need of Autowired.
	public PostService(PostRepository repository) {
		this.repository = repository;
	}

	public List<Post> getAllPosts() {
		return repository.findAll();
	}

	public Post getPostById(long id) {
		return repository.findById(id)
				.orElseThrow(() -> new ResourceNotFoundException("Post not found with the id " + id));
	}

	public Post createPost(Post post) {
		Post savedPost = repository.save(post);
		System.out.println("savedPost :  " + savedPost);
		return savedPost;
	}

	public Post updatePost(Long id, Post updatedPost) {
		return repository.findById(id).map(post -> {
			post.setTitle(updatedPost.getTitle());
			post.setContent(updatedPost.getContent());
			return repository.save(post);
		}).orElseThrow(() -> new RuntimeException("Post not found with the id " + id));
	}

	public void delete(Long id) {
		repository.findById(id)
				.orElseThrow(() -> new ResourceNotFoundException("Post not found with id: " + id));
		repository.deleteById(id);
	}
}
```

**And the custom ResponseStatus:**

```java
package com.example.demo.exception;

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ResponseStatus;


@ResponseStatus(HttpStatus.NOT_FOUND)  // This makes Spring return a 404 response

public class ResourceNotFoundException extends RuntimeException {

    private static final long serialVersionUID = 1L;


    public ResourceNotFoundException(String message) {
    
            super(message);
    
     }

}
```

## Step 9: Create the Controller:

```java
package com.example.demo.controller;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.model.Post;
import com.example.demo.service.PostService;

@RestController
@RequestMapping("api/posts")
public class PostController {
	private final PostService service;
	
	public PostController(PostService service) {
		this.service = service;
	}

	@GetMapping
	public List<Post> getAllPosts() {
		return service.getAllPosts();
	}
	
	@GetMapping("/{id}")
	public ResponseEntity<Post> getPostById(@PathVariable long id) {
		return ResponseEntity.ok(service.getPostById(id));
	}
	
	@PostMapping
	public ResponseEntity<Post> createPost(@RequestBody Post post) {
		Post savedPost = service.createPost(post);
		return ResponseEntity.status(HttpStatus.CREATED).body(savedPost);
	}
	
	@DeleteMapping("/{id}")
	public ResponseEntity<Void> deletePost(@PathVariable long id) {
		service.delete(id);
		return ResponseEntity.noContent().build();
		
	}
	
	@PutMapping("/{id}")
	public ResponseEntity<Post> updatePost(@PathVariable long id, @RequestBody Post post) {
		Post updatedPost = service.updatePost(id, post);
		return ResponseEntity.ok(updatedPost);
		
	}
}
```

Then restart the app and test it:

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXdlCbHLhY0WS-PkJbP3elvb_5selbaHv9BuQt_hWeAdBfbANcBOrFWaQc7F30DjiD0D-iOMVoq0GKdOTe_LJ4ImTZyCD2mjUnInEcohLPdleu1UDJmnWIVV_hzBY5kHgWIeeJP6ow?key=0w8rH2kOvftLEbAnzKioGVAR align="left")

**Now, let’s add some test data and configuration to pre-load some posts by default:**

Application properties:

```plaintext
#Automatically creates or updates tables based on your JPA entities.

spring.jpa.hibernate.ddl-auto=update

# Ensure Spring runs SQL scripts in the correct order (schema first and then data)

spring.sql.init.mode=always

# Ensure Hibernate fully initializes before data.sql runs.

spring.jpa.defer-datasource-initialization=true
```

src/main/resources/schema.sql

```sql
CREATE TABLE IF NOT EXISTS posts (
   id BIGINT AUTO_INCREMENT PRIMARY KEY,
   title VARCHAR(255) NOT NULL,
   content TEXT NOT NULL
);
```

src/main/resources/data.sql

```sql
INSERT INTO posts (title, content) VALUES ('First Post', 'This is the first post');
INSERT INTO posts (title, content) VALUES ('Second Post', 'This is the second post');
```

Finally, restart the application and invoke the posts endpoint:

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXfHGqb1wL43ZxQWapGFXGzMbNZ4b3MxPFCiZNkZE8VRtQof2Efu-D9_32DTlZ55-zfcfNAIHbEjn1yHK5nl9F4mjd20GC7lTSQCFPlLM3tdJmkpQ5wCDIiO-UQzMT-XwfQ8ONTP?key=0w8rH2kOvftLEbAnzKioGVAR align="left")

## Step 10: Create the remaining of the endpoints:

```java
    package com.example.demo.controller;
    
    import java.util.List;
    
    import org.springframework.http.HttpStatus;
    
    import org.springframework.http.ResponseEntity;
    
    import org.springframework.web.bind.annotation.DeleteMapping;
    
    import org.springframework.web.bind.annotation.GetMapping;
    
    import org.springframework.web.bind.annotation.PathVariable;
    
    import org.springframework.web.bind.annotation.PostMapping;
    
    import org.springframework.web.bind.annotation.PutMapping;
    
    import org.springframework.web.bind.annotation.RequestBody;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import org.springframework.web.bind.annotation.RestController;
    
    import com.example.demo.model.Post;
    
    import com.example.demo.service.PostService;
    
    @RestController
    
    @RequestMapping("api/posts")
    
    public class PostController {
    
    private final PostService service;
    
    public PostController(PostService service) {
    
        this.service = service;
    
    }
    
    @GetMapping
    
    public List<Post> getAllPosts() {
    
        return service.getAllPosts();
    
    }
    
    @GetMapping("/{id}")
    
    public ResponseEntity<Post> getPostById(@PathVariable long id) {
    
        return ResponseEntity.ok(service.getPostById(id));
    
    }
    
    @PostMapping
    
    public ResponseEntity<Post> createPost(@RequestBody Post post) {
    
        Post savedPost = service.createPost(post);
    
        return ResponseEntity.status(HttpStatus.CREATED).body(savedPost);
    
    }
    
    @DeleteMapping("/{id}")
    
    public ResponseEntity<Void> deletePost(@PathVariable long id) {
    
        service.delete(id);
    
        return ResponseEntity.noContent().build();
    
    }
    
    @PutMapping("/{id}")
    
    public ResponseEntity<Post> updatePost(@PathVariable long id, @RequestBody Post post) {
    
        Post updatedPost = service.updatePost(id, post);
    
        return ResponseEntity.ok(updatedPost);
    
    }    
}
```

**Finally, we can test everything using Postman:**

Verified all endpoints return **expected JSON responses**.

Ensured **correct HTTP status codes** (200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found).

For example:

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcJAO4RWqetb-2UXvjrSO4BUtto2ki-QFt8_0nA-U5lPorCI-bChy7E3Heg-Tm37VsuQF12UB5KI3gv-WQWJJcasoXAeQ2naPrnX2D8Je1tkZC9rWGNNCk7pwAI-r0TyvVQd_hd8Q?key=0w8rH2kOvftLEbAnzKioGVAR align="left")

Github repo with this code on the commit [https://github.com/mdjc/blog-posts-app/commit/79b4ef269b23968ff6cf6c2627e80a183758bd46](https://github.com/mdjc/blog-posts-app/commit/79b4ef269b23968ff6cf6c2627e80a183758bd46)
