The basics of REST API Spring boot

Nicollet Njora
6 min readFeb 6, 2020

This is a short continuous project on how to create a simple REST API Server in Spring boot.

To start with is a recap on OOP as these concepts will be vital in this short project and exercise.

OOP Recap

If you need to recap, check out this summary of the most common OOP concepts https://www.educative.io/edpresso/what-is-objectoriented-programming

Project

Having recapped all these useful concepts, it is time to now put them to work.

I will walk you through making a simple REST API that exposes resources via the internet and then I will give you an exercise that requires you to use the same concepts.

Create a new Gradle based spring boot project and add the following dependencies in the build.gradle file:

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
// Depending on which database you prefer
runtimeOnly 'org.postgresql:postgresql'

Creating Models

Create a package for models to put all the model classes in. I have a model University that has the attributes name, year founded and location. You need to create getters and setters for the attributes. It will look as follows:

The next step is to create a model for Students with first name, last name and date of birth as its attributes.

Relationships

We now have two classes that do not know about each other. We need to declare the relationships between a Student and a University. A student can only be in one university but a university has many students. It is, therefore, a one to many relationship.

On the University model have a list of students that associated with the university as shown below.

private List<Student> students;public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}

In the Student model, have a university object to indicate where a particular student is enrolled. It should look as follows.

private University university;public Student(String firstName, String lastName, University university) {
this.firstName = firstName;
this.lastName = lastName;
this.university = university;
}
public University getUniversity() {
return university;
}
public void setUniversity(University university) {
this.university = university;
}

Persisting Data

To persist data, you annotate the class declaration as an entity and also have the table annotation where you specify the name of the table in the database.

@Entity
@Table(name = "students")
public class Student {
// ..... the rest of the content
}

Every entity should have a primary key, for that annotate the id attribute as follows:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;

As for the other attributes, annotate with the column annotation only and specify the name of the column(using snake_case). For example:

@Column(name = "first_name")
private String firstName;

In the University model, annotate the student attribute with OneToMany as follows:

@OneToMany(mappedBy = "university")
private List<Student> students;

You should map the relationship both ways so that you can access the relationship from both models. The Student model will have the university_id column to point to the university the student is enrolled in. It will look as follows:

@ManyToOne
@JsonIgnore
@JoinColumn(name = "university_id")
private University university;

Note that this has the JsonIgnore annotation, this is to avoid an infinite JSON object as a Student will call the University object it is associated to and the university object will call all the student objects it is associated to…..it goes on and on.

Database

You are most probably having a “cannot resolve” error on the table names and column names. To get rid of this, add a data source to the project. If you are not familiar with doing this check out this tutorial.

After adding the database, add your DB credentials in the application.properties file which you can find in the src/main/resources folder. It will look similar to this:

server.port=8080
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://localhost:5432/rest
spring.datasource.username=usernamedropdrop
spring.datasource.password=password

Repositories

Now we have tables with columns but how do they connect to the database to do the CRUD functions? That is where repositories come in.

Create an interface repository for each of the entities that extend JpaRepository as shown below:

public interface UniversityRepository extends JpaRepository<University, Long> {
}

Remember to put the repositories in their package.

Services

Next is to create service interfaces that define the methods we will use for the CRUD functions. University Service will look as follows:

public interface UniversityService {List<University> findAll();

University findById(Long id);

void delete(Long id);

University createUniversity(University university);

University update(Long id, University university);
}

Create a similar interface for the Student model.

Now to connect our CRUD functions to the database, we implement the service interface and inject the repository to use its inherited functions. Do not forget to annotate the service implementation class with the service annotation. The UniversityServiceImpl will look as follows:

Notice there is a NotFoundException class called. Create the class either in its own exceptions package or the root package. It will look as follows:

Implement the same for the StudentService interface.

Controllers

Create a package for the controllers and create the UniversityController class. This is where we will expose endpoints.

In the controller, annotate the class declaration as a RestController and also add the RequestMapping annotation with the value as universities. This tells Spring that this class will be expecting requests over the network and will handle the requests directed to “yourbasedomain/universities” in this case “http://localhost:8080/universities” if your app is configured to run in port 8080.

To use the functionalities implemented in the service implementation class, inject the UniversityService class and require it in the constructor.

The UniversityController now looks as follows:

@RestController
@RequestMapping(value = "universities")
public class UniversityController {
private final UniversityService universityService;
public UniversityController(UniversityService universityService)
{
this.universityService = universityService;
}
}

The different types of requests are structured as follows:

GET

@GetMapping
public List<University> findAll(){
return universityService.findAll();
}

POST

@PostMapping
public University createUniversity(@RequestBody University university) {
return universityService.createUniversity(university);
}

PATCH

Note that this request URL will be similar to http://localhost:8080/universities/{id} with the method PATCH

@PatchMapping(value = "{id}")
public University updateUniversity(@PathVariable Long id, @RequestBody University university) {
return universityService.update(id, university);
}

DELETE

@DeleteMapping(value = "{id}")
public void deleteUniversity(@PathVariable Long id) {
universityService.delete(id);
}

Do the same for the StudentController.

Validation

You may have noticed that we haven’t done much validation of the requests coming in. It is important to validate payloads coming in so that you can gracefully handle the errors likely to occur and also you do not end up having ununiform records.

We will implement validation from the model and use it when getting a request in the controller.

In the model, create an interface called Create as shown below:

@Entity
@Table(name = "students")
public class Student {
// ..... the rest of the contentpublic interface Create{}
}

We will use the create interface to validate that the required attributes are present when creating. To do that, add a NotNull annotation before the attributes as follows:

public class University {// ... the rest of the content

@NotNull(groups = Create.class)
@Column(name = "name")
private String name;
@NotNull(groups = Create.class)
@Column(name = "location")
private String location;
// ... the rest of the content
}

In the controller, in the createUniversity method, add a Validated annotation and call the Create interface from the University class as shown below:

@PostMapping
public University createUniversity(@Validated(University.Create.class)
@RequestBody University university) {
return universityService.createUniversity(university);
}

Do the same for the Student Model. Only put NotNull annotation to the attributes that have to be there.

It makes more sense to create a student under the particular university they are enrolling in. So to cater for this add a createStudent method on the UniversityService and do not forget to implement it and add an endpoint POST “/universities/{id}/students”

The UniversityServiceImpl looks as follows:

private final StudentService studentService;public UniversityServiceImpl(UniversityRepository universityRepository, StudentService studentService  ) {
this.universityRepository = universityRepository;
this.studentService = studentService;
}
// ...the rest of the content@Override
public Student createStudent(Long universityId, Student student) {
University university = findById(universityId);
student.setUniversity(university);
return studentService.createStudent(student);
}

Note that we pass in the student service instead of the repository.

The endpoint on the UniversityController will look as follows:

@PostMapping(value = "{id}/students")
public Student createStudent(@PathVariable Long id,
@Validated(Student.Create.class)
@RequestBody Student student){
return universityService.createStudent(id, student);
}

Testing

You can test the endpoints exposed using Postman. Make sure you include all the parameters.

Notice that if you send the request without the required attributes it returns an error 400 BAD REQUEST with a message that the particular attribute cannot be null.

Exercise

You can fork the repo from here then go on to do the exercise below.

Introduce another entity, Course. A student can enrol in multiple courses and a Course can have many students.

Expose endpoints to:

  • Allow Students to view the courses available.
  • Enrol in a course using the course’s ID.

Ensure you cater for:

  • Validation when creating a record
  • Many to many relationship between Student and Course

Hint: Many to many relationship needs a pivot table AKA a join table where you need to declare the join column and the inverse join column, i.e course_id and student_id respectively.

@ManyToMany 
@JoinTable(name= “student_courses”, joinColumns = @JoinColumn(name = “course_id”), inverseJoinColumns = @JoinColumn(name=”student_id”) ) @JsonIgnore
private Set<Student> students = new HashSet<>();

--

--

Nicollet Njora

Software Engineer. Baker. Life enthusiast. Seeing Fuchsia makes me Flutter 😅😉.