Table of Contents

Springboot - 01. Membangun Layanan Restful API

Requirement

  1. Java LTS 17, 21 or later. Pada 2024 versi LTS terakhir versi 21.

  2. Gradle 7.5+ or Maven 3.5+

  3. You can also import the code straight into your IDE:

Memulai dengan Spring Initializr

Buka laman web https://start.spring.io. Lalu inisialisasi projek yang ingin dibuat.

Kita akan membuat Project Maven, dengan bahasa pemrograman Java, lalu versi Spring Boot yang dipilih adalah versi 3.3.2.

Download lalu extract hasil generate project di atas.

Memulai Project

IntelliJ IDEA

Buka Editor anda di bawah ini merupakan contoh jika menggunakan IntelliJ IDEA

Dependency

Selanjutnya kita perlu menambahkan beberapa dependency atau library yang akan digunakan ke dalam file pom.xml.

Dependency ini bertujuan untuk bisa mengakses ke dalam database yang sudah dikonfigurasi.

Penambahan juga dapat dilakukan saat sebelum mengenerate project pada Spring Initializr.

				
					<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-devtools</artifactId>
   <scope>runtime</scope>
</dependency>
<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<scope>provided</scope>
</dependency>
				
			
Application Properties

Selanjutnya tambahkan properties, setup project pada application.properties seperti berikut

				
					spring.application.name=hello
# changing port
server.port=7000
# changing port
server.servlet.context-path=/v1/api

# setup database connection
# driver database
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# database url connection
spring.datasource.url=jdbc:mysql://localhost:3306/demo
# database username
spring.datasource.username=root
# database password
spring.datasource.password=root

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format-sql=true
				
			
  • server.port digunakan untuk merubah port API yang digunakan, defaultnya port yang digunakan adalah 8080.
  • server.servlet.context-path digunakan untuk menambahkan context path pada endpoint API, default property ini adalah string kosong. Pada tutorial, kita menambahkan context path yaitu /v1/api maka, endpoint API kita dapat diakses pada localhost:8000/v1/api/{endpoint}.
  • spring.datasource merupakan property untuk setup connection ke database yang mau digunakan
    — driver-class-name, property untuk menentukan driver database yang digunakan.
    — url, untuk url jdbc connection databasenya. Format url yang dituliskan adalah seperti berikut jdbc:db_type://host_name:port/db_name.
    Perlu diingat, untuk database perlu dibuat terlebih dahulu pada postgresql-nya, dengan nama yang sama pada setup connectionnya, sebagai contoh di application properties kita menuliskan nama database dengan db_library, maka pada postgresql kita juga membuat database dengan nama yang sama.
    — username, credential username database.
    — password, credential password database.
  • spring.jpa merupakan property untuk setup JPA
    — show-sql, bernilai true untuk menampilkan syntax sql yang sedang dieksekusi, defaultnya bernilai false.
    — hibernate.ddl-auto, bernilai update untuk menjalankan perintah DDL SQL table, seperti create table secara otomatis selama ada perubahan / perbedaan dalam schema tablenya.
    — properties.hibernate.dialect, berisi dialect database yang digunakan.
    — properties.hibernate.format-sql, bernilai true untuk memformat penulisan sql supaya mudah dibaca.
Struktur Project
				
					src
  +- main
  |  +- java
  |  |  +- com
  |  |     +- ombagoes
  |  |        +- blog
  |  |           +- dtos
  |  |           +- controllers
  |  |           |  +- PostController.java
  |  |           +- models
  |  |           |  +- Post.java
  |  |           +- repositories
  |  |           |  +- PostRepository.java
  |  |           +- BlogApplication.java
  |  +- resources
  |     +- application.properties
  +- test
				
			

Di dalam root projectnya akan dibuat beberapa packages atau folder, seperti

  1. Controllers, untuk menyimpan controller REST API yang berisi route path APInya.
  2. Models bertujuan untuk menyimpan data model dan juga sebagai entitas table yang akan terhubung ke databasenya.
  3. DTO bertujuan sebagai object yang digunakan untuk request dan response data yang akan dikembalikan.
  4. Repositories bertujuan sebagai Object Relational Mapping (ORM) setiap entitas yang ada di database dengan project, dengan menggunakan dependency JPA Repository
  5. Untuk application.properties akan berisi properties apps maupun environments yang dibutuhkan.
  6. pom.xml file ini akan berisi dependency dan plugin yang dibutuhkan project.
Model

Buat package baru bernama models sesuaikan dengan Struktur Direktori, lalu buat file class bernama Post.

models/Post.java

				
					package com.ombagoes.hello.models;

import jakarta.persistence.*; 
import lombok.Getter;
import lombok.Setter;

@Getter
@Entity
@Table(name = "posts")
@NoArgsConstructor
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Setter
    private String title;

    @Setter
    private String description;

    @Setter
    private boolean published;

    @Column(name = "created_at")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdAt;

    @Column(name = "updated_at")
    @Temporal(TemporalType.TIMESTAMP)
    private Date updatedAt;

    @PrePersist
    protected void onCreate() {
        createdAt = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = new Date();
    }

    //DTO
    public Post(String title, String description, boolean published) {
        this.title = title;
        this.description = description;
        this.published = published;
    }

    @Override
    public String toString() {
        return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
    }

}

				
			
Lombok

Pada pom.xml sebelumnya kita sudah menambahkan dependency lombok. Fungsinya adalah untuk menggenerate fungsi sbb :

@Getter & @Setter, Menggenerate fungsi Get dan Set untuk field secara otomatis. Untuk case di atas Get digenerate di semua field, sementara Setter hanya pad a field yang ditambahkan Annotation @Setter di atas field.

@NoArgsConstructor Menggenerate constructor default kosong pada Class. jika anda menggunakan semua file tambahkan @AllArgsConstructor untuk menggantikan constructor public Post(String title, ...){...} di atas, tidak digunakan karena tidak sesuai. Akan berguna jika anda membuat Dto.

Simplenya tanpa lombok kita harus membuat beberapa fungsi seperti berikut :

				
					public class Post{
    ...
    //NoArgsConstructor
    public Post() {
    
    }

    //Getters and Setters
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    //dst. untuk semua field
    ...
}
				
			

@Data merupakan generator untuk memanggil generator lain @Getter@Setter@NoArgsConstructor, dan @AllArgsConstructor , Jadi jika anda memerlukan semuanya cukup memangil Anotasi @Data

				
					import lombok.Data;

@Data
public class ContohKelas {
    private String title;
    private String description;
    private boolean published;
}
				
			

Biasa digunakan saat membuat DTO(Data Transfer Object).

Validation

Pada pom.xml sebelumnya kita sudah menambahkan dependency spring-boot-starter-validation

				
					    <dependencies>
        ...
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
	</dependencies>
				
			

Tambahkan anotasi berikut ke dalam models/Post.java

				
					...
public class Post {
    ...
    @Setter
    @NotBlank(message = "title cannot empty")
    @Pattern(regexp = "/^[\\w\\s]+$/", message = "title only allow AlphaNum Char.")
    private String title;
    ...
}

				
			
Repository

Buat package baru bernama repositories sesuaikan dengan Struktur Direktori, lalu buat file interface bernama PostRepository  dengan extends JpaRepository

models/PostRepository.java

				
					package com.ombagoes.hello.repositories;

import com.ombagoes.hello.models.Post;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface PostRepository extends JpaRepository<Post, Long> {
    List<Post> findByPublished(boolean published);
    List<Post> findByTitleContaining(String title);
}

				
			

Kita memiliki method asli :

  1. findByPublished
  2. findByTitleContaining
List dapat dilihat disini

Ditambahkan dengan method bawaan JpaRepository:

  1. save(),
  2. findOne(),
  3. findById(),
  4. findAll(),
  5. count(),
  6. delete(),
  7. deleteById()

Tanpa menuliskannya di dalam interface.

Controller

Buat package baru bernama  controllers sesuaikan dengan Struktur Direktori, lalu buat file class bernama PostController.

controllers/PostController.java

				
					package com.ombagoes.hello.controllers;

import com.ombagoes.hello.models.Post;
import com.ombagoes.hello.repositories.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
public class PostController {
    @Autowired
    PostRepository postRepository;

    @GetMapping("/posts")
    public ResponseEntity<List<Post>> getAllPosts(@RequestParam(required = false) String title) {
        try {
            List<Post> Posts = new ArrayList<Post>();

            if (title == null)
                Posts.addAll(postRepository.findAll());
            else
                Posts.addAll(postRepository.findByTitleContaining(title));

            if (Posts.isEmpty()) {
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

            return new ResponseEntity<>(Posts, HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @GetMapping("/posts/{id}")
    public ResponseEntity<Post> getPostById(@PathVariable("id") long id) {
        Optional<Post> postData = postRepository.findById(id);

        return postData.map(post -> new ResponseEntity<>(post, HttpStatus.OK)).orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    @PostMapping("/posts")
    public ResponseEntity<?> createPost(@RequestBody @Valid Post post, , BindingResult bindingResult) {
        try {
            if (bindingResult.hasErrors()) {
                List<String> exceptionalErrors= bindingResult
                    .getFieldErrors()
                    .stream()
                    .map(x -> x.getDefaultMessage())
                    .collect(Collectors.toList());
            map.put("errors", exceptionalErrors);
                map.put("success", false);
                return new ResponseEntity<>(map, HttpStatus.BAD_REQUEST);
            }
            Post _post = postRepository
                    .save(new Post(post.getTitle(), post.getDescription(), post.isPublished()));
            return new ResponseEntity<>(_post, HttpStatus.CREATED);
        } catch (Exception e) {
            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @PutMapping("/posts/{id}")
    public ResponseEntity<Post> updatePost(@PathVariable("id") long id, @RequestBody Post post) {
        Optional<Post> postData = postRepository.findById(id);

        if (postData.isPresent()) {
            Post _post = postData.get();
            _post.setTitle(post.getTitle());
            _post.setDescription(post.getDescription());
            _post.setPublished(post.isPublished());
            return new ResponseEntity<>(postRepository.save(_post), HttpStatus.OK);
        } else {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    @DeleteMapping("/posts/{id}")
    public ResponseEntity<HttpStatus> deletePost(@PathVariable("id") long id) {
        try {
            postRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @DeleteMapping("/posts")
    public ResponseEntity<HttpStatus> deleteAllPosts() {
        try {
            postRepository.deleteAll();
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }

    }

    @GetMapping("/posts/published")
    public ResponseEntity<List<Post>> findByPublished() {
        try {
            List<Post> posts = postRepository.findByPublished(true);

            if (posts.isEmpty()) {
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
            return new ResponseEntity<>(posts, HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

				
			
  1. @CrossOrigin digunakan untuk mengatur asal client yang diizinkan.
  2. @RestController digunakan untuk mendefinisikan pengontrol.
  3. @RequestMapping(“/api”) menyatakan bahwa semua url Apis di pengontrol akan dimulai dengan /api.
  4. @Autowired digunakan untuk memasukkan kerangka PostRepository ke variabel lokal.
List Endpoint
MethodsUrlsActions
POST/api/postsMembuat Post baru
GET/api/postsMendapatkan semua data Post
GET/api/posts/:idMendapatkan 1 data Post berdasarkan :id
PUT/api/posts/:idMengubah1 data Post berdasarkan :id
DELETE/api/posts/:idMenghapus 1 data Post berdasarkan :id
DELETE/api/postsMenghapus semua Post
GET/api/posts/publishedMendapatkan semua data Post yang sudah di publish
GET/api/posts?title=[keyword]Mendapatkan Semua data Post berdasarkan keyword

 

Referensi