Yo! Fellowdevs, welcome to the program! Pada program ini, kita akan belajar mengenai bagaimana membuat sebuah aplikasi todo sederhana berbasis web yang terdiri dari backend dan frontend dengan menggunakan Spring Boot dan Angular. So, tanpa menunda-nunda lagi, let's start creating our API ๐จ
Prerequisites
Sebelum memulai masuk lebih dalam, ada beberapa program yang perlu kita install terlebih dahulu, di antaranya:
- Java Development Kit (JDK) versi 11 or later (link)
- Maven (how to)
- IDE (sesuai preferensi)
- PostgreSQL, saya menggunakan versi 12 (link)
- Postman, opsional (link)
Menjelaskan instalasi masing-masing program tersebut akan memakan waktu dan membuat tulisan ini menjadi lebih panjang dan melebar, so saya harap fellowdevs bisa mencari referensi di internet ya, banyak sekali kok ๐
Disclaimer
Perlu diketahui bahwa pada program ini saya tidak menjelaskan mengenai tutorial, misalnya tutorial Java, tutorial Spring atau tutorial Angular. Namun apa yang saya jelaskan lebih ke bagaimana implementasi framework tertentu pada aplikasi sederhana dan integrasinya berdasarkan konsep yang dibahas, seperti misalnya model arsitektur, mengapa menggunakan stack tertentu, desain struktur program atau do's and don'ts.
Hal ini saya tujukan karena materi seperti tutorial-tutorial tersebut sangat mudah fellowdevs dapatkan melalui platform online course seperti Udemy atau bahkan video gratis seperti Youtube. Nyatanya, saya sendiri pun belajar banyak dari platform-platform tersebut. Akan sangat memakan waktu dan tenaga apabila saya berusaha membuat tutorial serupa yang berulang dimana saya sendiri tidak begitu yakin bahwa content yang saya deliver akan lebih baik dari para creator tersebut.
Ditambah lagi, saya ingin berfokus pada hal-hal lain yang lebih diperlukan pada dunia industri profesional. Misalnya seperti desain arsitektur aplikasi, security, common practices, tips & trik, atau bahkan sekedar memperkenalkan tools atau teknologi yang mungkin belum dikenal sebelumnya namun banyak digunakan di pekerjaan nantinya.
Oleh karena itu, saya mendorong fellowdevs peserta program untuk dapat lebih dahulu mempelajari konsep dasar dari stack yang akan digunakan, misalnya seperti mengerti konsep dasar Java, OOP, Javascript, database, dll. Hal ini akan membantu fellowdevs dalam mengikuti program ini dengan lebih efektif.
Backend For Frontend
Arsitektur FEBE, as I usually call it, atau juga dikenal BFF, terdiri dari paling sedikit dua bagian utama, yaitu frontend dan backend. Backend biasanya berperan sebagai perantara dalam bentuk API untuk berhubungan dengan bagian "dalam" aplikasi seperti database, authorization manager, atau bahkan aplikasi lain. Sedangkan frontend akan berperan sebagai user interface bagi pengguna agar dapat berinteraksi dengan aplikasi kita. Arsitektur ini sudah sangat common digunakan dalam membangun aplikasi terutama aplikasi berbasis web.
Tapi, kenapa kita perlu memisahkan frontend dengan backend sih?
Ada beberapa alasan kenapa kita perlu melakukan pemisahan ini. Berdasarkan pengalaman saya pribadi, dua hal terpenting untuk melakukan separasi ini adalah alasan Scalability dan Modularity. Namun, di artikel ini saya tidak akan membahas mengenai hal tersebut, kita akan bahas lagi lain waktu.
Sebagai referensi, fellowdevs dapat membaca dari sini untuk memulai.
Gambaran arsitektur aplikasi yang akan kita bangun berdasarkan pemisahan tersebut adalah sebagai berikut.
Teknologi yang akan kita gunakan untuk membangun aplikasi sederhana kita adalah Spring Boot untuk backend, dimana kita akan menggunakan komponen-komponen seperti Spring Web MVC untuk membuat REST API, Spring Data JPA untuk berinteraksi dengan database kita.
Untuk database, kita akan menggunakan PostgreSQL saja untuk kemudahan.
Apabila kamu belum familiar dengan API, monggo untuk baca dulu artikel ini.
Nah, aplikasi yang akan kita buat adalah aplikasi to-do list sederhana dimana pengguna dapat menambahkan item ke daftar to-do dan mengubah status item to-do. Kurang lebih, API yang akan kita buat nantinya adalah berikut ini.
Method | Endpoint | Action |
GET | /adam/todo | Mendapatkan daftar task |
POST | /adam/todo | Menambahkan task ke dalam todo list |
PUT | /adam/todo | Mengubah status dari sebuah task (digunakan untuk done / undone) |
Why Java/Spring?
Java adalah bahasa pemrograman yang dapat dibilang "tua", dan di zaman sekarang ini banyak developer yang mungkin sudah tidak menggunakannya lagi. Berkaitan dengan hal tersebut, fellowdevs dapat membaca di artikel ini mengenai mengapa saya masih menggunakan Java hingga saat ini๐
Lalu mengapa Spring? Untuk jawabannya, fellowdevs tidak akan kesulitan mendapatkannya di internet. Namun dalam perjalanan saya bersama Java selama kurang lebih 10 tahun ini, tidak ada yang lebih mudah melakukan Java development daripada menggunakan Spring sebagai framework-nya.
Menurut saya, sepertinya di luar sana Spring sudah menjadi "de facto" nya Java framework. Memang banyak alternatif lain selain Spring, seperti Struts, JSF, Vaadin, Grails, you can name it. But with Spring, kita mendapatkan pengalaman pemrograman yang lebih mudah, lebih cepat, dan lebih aman.
Untuk lebih lengkap silakan cek link ini, Why Spring?
Untuk membuat aplikasi Spring Boot, Spring telah menyediakan sebuah generator yang memudahkan kita untuk memulai, yaitu Spring Initializr. Sebenarnya ada juga cara lain seperti membuat sebuah Maven project kemudian kita tambahkan Spring Boot dependency-nya, tapi for the sake of simplicity kita pakai Initializr saja ya โ๐
Pada Spring Initialzr, saya akan menggunakan Maven untuk build tool-nya,
jar
sebagai pilihan packaging-nya, and of course Java untuk language-nya dengan Java 11 untuk versinya. Untuk versi Java sendiri fellowdevs dapat memilih versi java 8 atau bahkan 17. Namun saat blog ini saya tulis, Java 11 adalah versi menengah yang tidak terlalu tertinggal dan juga tidak terlalu baru (cenderung stabil).Metadata projeknya dapat diisi seperti ini:
- Group:
com.bca
- Artifact:
adam
- Name, Description, Package Name: default
Nah kemudian, kita perlu untuk menambahkan dependency apa saja yang kita butuhkan dalam project kita. Kalau kalian bingung dengan istilah dependency, anggaplah dependency sebagai kumpulan code yang telah ditulis oleh developer lain yang dapat kita reuse agar kita tidak perlu membuatnya lagi. Dependency ini dapat juga kita sebut sebagai library apabila itu lebih mudah untuk fellowdevs pahami.
Untuk menambahkan dependency, fellowdevs tinggal klik saja di tombol
Add Dependencies
atau tekan Ctrl + B
untuk shortcut, lalu tinggal ketik nama dependency yang kita ingin tambahkan.Dependency yang akan kita gunakan adalah:
- Spring Web (komponen Spring utama yang kita butuhkan untuk membuat aplikasi Spring Web atau REST API dengan Spring)
- Spring Data JPA (diperlukan untuk berinteraksi dengan database tanpa harus membuat koneksi dan query secara manual)
- Lombok (sebuah library yang sangat berguna untuk memudahkan kita dalam menuliskan program Java, terutama untuk mempersingkat dan mengurangi redundansi yang tidak perlu)
- PostgreSQL Driver (driver yang diperlukan untuk koneksi ke database kita yaitu Postgres)
- Spring Boot Devtools (library ini akan memudahkan kita dalam proses development terutama untuk hot reload)
And that's it! Just to sum it up, berikut adalah konfigurasi Spring Initialzr kita.
Now all we need to do is click
Generate
atau Ctrl + โ
yang kemudian browser kita akan secara otomatis men-download project tersebut.Getting started with Our API Development
Okay, setelah project dari Spring Initializr berhasil kita download ke local machine kita, mari kita extract (it is a
.zip
file) dan load project-nya ke IDE kita.What IDE should I Use?
Talking about IDE, bagi saya, IDE ibarat agama, jadi saya tidak mengharuskan fellowdevs untuk mengikuti dengan IDE yang saya gunakan. Namun agar lebih mudah, saya akan menggunakan VSCode karena IDE ini sangat populer saat ini. Apabila fellowdevs tidak punya preferensi IDE dan ingin menggunakan VSCode juga, silakan kalian dapat download dan install dulu di sini.
Project Structure
Untuk membuka project Spring kita ke IDE ada banyak cara, namun secara umum, cari opsi seperti Open Project atau Open Folder pada IDE yang fellowdevs gunakan, lalu arahkan ke folder project hasil extract tadi.
Untuk VSCode sendiri, fellowdevs dapat menggunakan cara yang lebih singkat dengan klik kanan pada folder project kita dan pilih menu Open with Code.
Nah, sebelum kita melangkah lebih dalam, ada baiknya kita lebih dulu mengerti apa saja file-file yang ada di dalam project kita, terutama bagi fellowdevs yang belum familiar dengan Spring framework.
. โโโ .mvn โโโ src โ โโโ main โ โ โโโ java โ โ โ โโโ com/bca/adam โ โ โ โโโ AdamApplication.java โ โ โ โโโ ServletInitializer.java โ โ โโโ resources โ โ โโโ static โ โ โโโ templates โ โ โโโ application.properties โ โโโ test โโโ .gitignore โโโ HELP.md โโโ mvnw โโโ mvnw.cmd โโโ pom.xml
Most of the time, kita akan bekerja pada folder
/src/
. Di luar folder ini, satu-satunya file yang mungkin akan kita ubah adalah file pom.xml
. Itu pun kalau memang ada dependency baru atau ada konfigurasi Maven khusus yang perlu dilakukan.Di dalam folder
/src/
, terdapat dua folder lain yaitu /main
dan /test
. Folder /test
ini tidak akan saya bahas dulu karena akan menambah kompleksitas project, basically folder ini akan menampung semua file hasil generate saat proses automated testing oleh Maven. Dan karena kita tidak menggunakannya, kita hapus saja.Kemudian di dalam folder
/main
, akan ada dua folder lagi yaitu /java
dan /resources
. Folder /resources
akan menampung file-file pendukung yang akan digunakan oleh aplikasi, misalnya seperti file konfigurasi, assets, templates, etc. Nah, ada satu file di dalam folder /resources
yang juga disediakan untuk kita, yaitu application.properties
. File ini biasanya kita gunakan untuk konfigurasi yang akan dibaca oleh Spring saat proses bootup. File ini tidak mandatory namun biasanya akan digunakan sekalipun dalam aplikasi sederhana. FYI, file ini dapat berupa .yml
juga ya.Selanjutnya kita akan masuk ke dalam main course-nya, yaitu di dalam folder
/java
. Kita akan menemui subfolders yang sesuai dengan packaging yang kita tentukan di Initializr, yaitu /com/bca/adam/
. Di dalam folder inilah kita akan fokus untuk development kita.How Spring application starts?
Di dalam folder
/adam/
, ada dua file .java
yang sudah di-generate oleh Spring. Apakah fungsi dua file ini? Apakah kita bisa modifikasi?Nah, hal ini berkaitan dengan bagaimana sebuah aplikasi Spring berjalan. Pada saat proses bootup dimulai (kita akan jelaskan nanti), most frameworks akan melakukan apa yang disebut dengan bootstraping. Sederhananya, proses ini berjalan secara internal dimana aplikasi akan mulai membaca semua konfigurasi yang diperlukan atau yang telah di-define oleh developer kemudian me-load konfigurasi tersebut ke dalam sistem sehingga dapat digunakan untuk memulai. Thanks to Spring Boot Starter, developer tidak perlu pusing mengenai bagaimana proses bootstraping oleh Spring Boot berjalan.
Loh kapan kita mendefinisikan Spring Boot Starter ini? Kita kan belum menyentuh apapun dari project ini? Nah, kalau kita lihat di dalam file
pom.xml
, kita akan menemukan dependency spring-boot-starter-web
pada segment dependencies. Dependency inilah yang memungkinkan developer tidak perlu melakukan banyak konfigurasi manual pada aplikasi Spring Boot. Hal ini juga berlaku pada dependency spring-boot-starter-jpa
yang juga kita define, dependency ini akan memudahkan kita dalam melakukan transaksi dengan database.<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ... </dependencies>
Spring Boot akan memulai prosesnya melalui class yang diberikan anotasi
@SpringBootApplication
. Inilah entry point setiap aplikasi Spring Boot. Di dalam file AdamApplication.java
, fellowdevs dapat melihat sendiri bahwa di dalam class tersebut sudah dianotasikan demikian oleh Spring Initializr, sehingga aplikasi kita akan berjalan melalui class ini.Kemudian, anotasi
@ComponentScan
digunakan untuk memberi tahu Spring bahwa Spring perlu melakukan scanning terhadap components yang terdapat pada direktori / path yang disebutkan dalam parameter. Apabila tidak ada anotasi ini, Spring tidak akan tahu lokasi component-component yang kita buat dan sehingga component tersebut tidak dapat digunakan. Dalam hal ini, kita akan menggunakan component-component di dalam package com.bca.adam
.Pada konsep Java, untuk kita dapat run aplikasi, kita memerlukan method yang disebut sebagai main method. Di class
AdamApplication.java
, main method juga telah dituliskan dan didalamnya terdapat fungsi SpringApplication.run(primarySource, args)
dimana method ini akan menginstrusikan Spring app untuk berjalan.@SpringBootApplication public class AdamApplication { public static void main(String[] args) { SpringApplication.run(AdamApplication.class, args); } }
Kembali ke pertanyaan awal, sekarang kita sudah tahu fungsi dari file
AdamApplication.java
, kemudian apa fungsi dari file ServletInitializer.java
? Sebenarnya, pada project sederhana kita kali ini, kita belum menggunakannya. Namun secara singkat, file ini adalah konfigurasi yang akan kita butuhkan apabila kita melakukan deployment aplikasi kita dengan menggunakan servlet container/web server, seperti Tomcat, JBoss dan lain-lain.Biasanya kita menginginkan cara run tradisional seperti
WAR
archive. Namun karena kita tidak melakukan deployment tersebut, sebenarnya kita hapus pun tidak masalah. Sekedar informasi, bila kita hendak melakukan deployment dengan servlet container, maka kita memerlukan konfigurasi dengan meng-extend SpringBootServletInitializer
. Additional information, by default Spring Boot hadir dengan embedded servlet container yakni Tomcat yang runnable with zero configuration dan inilah yang akan kita gunakan.Nah setelah mengetahui bahwa kedua file
.java
yang ter-generate ini adalah untuk konfigurasi aplikasi Spring Boot kita, apakah file-file ini bisa diubah? Tentu bisa, karena ini adalah konfigurasional, tentu saja at some points fellowdevs akan menemui waktu dimana diperlukan untuk modifikasi konfigurasi bawaan ini.Simple API Test
Salah satu kelebihan menggunakan Spring Boot adalah konfigurasinya yang otomatis tersedia bahkan tanpa perlu menambahkan konfigurasi khusus kecuali memang kita membutuhkannya. Hal ini juga berlaku untuk pembuatan backend project yang akan kita kerjakan ini. Kalau dibandingkan dengan Spring framework-nya saja, Spring Boot sudah sangat membantu jauh dalam banyak aspek, apalagi kalau dibandingkan dengan native Java project. Saya sih ogah ๐.
Sebelum kita masuk ke development yang kompleks, yuk kita coba membuat sebuah simple API (Hello World API) untuk membuktikan hal tersebut.
Buat sebuah folder
/package
di dalam package com.bca.adam
dengan nama controller
. Penamaan dan struktur folder ini sebenarnya tidak mandatory, hanya saja akan lebih rapi dan bersih kalau kita klasifikasikan komponen-komponen project kita ke dalam package yang sesuai. Lalu di dalam controller
, Java class dengan nama HelloWorldController.java
. Kembali saya terangkan bahwa penamaan ini tidaklah mandatory, suffix Controller
pada Java class kita akan membantu kita nantinya terutama apabila project kita menjadi lebih besar dan kompleks.Class
HelloWorldController.java
akan menjadi controller untuk API Hello World kita. Controller sendiri dapat fellowdevs anggap sebagai route pada URL kita nantinya. Gambaran sederhananya seperti ini.Ada banyak kombinasi lain yang dapat fellowdevs temukan di dokumentasi Spring official.
Sekarang, kita perlu memberi tahu Spring bahwa class ini adalah sebuah controller. Bagaimana caranya? Tentu sangatlah mudah. Spring menggunakan banyak abstraksi dalam bentuk anotasi (
@....
) untuk menentukan jenis komponen sebuah object. Hal ini disebut juga sebagai stereotype. Untuk membuat REST API, kita akan gunakan anotasi @RestController
yang akan kita arahkan ke route /hello
untuk memisahkan dari controller utama kita nanti.Tambahkan anotasi
@RestController
ke controller kita ini. Lalu kita juga perlu menambahkan anotasi @RequestMapping
untuk menentukan route ke API kita. Singkatnya, anotasi ini kita gunakan dalam menentukan routing ke API yang kita buat. Baca juga artikel ini untuk penjelasan yang lebih mendetil. Route yang akan kita gunakan adalah /hello
, maka kita tambahkan parameter "hello"
di dalam anotasi @RequestMapping
.package com.bca.adam.controller; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; @RestController @RequestMapping("hello") public class HelloWorldController { }
Jangan lupa ya pastikan IDE kalian menambahkan import statement nya secara otomatis. Apabila tidak, maka kalian perlu menambahkan import tersebut secara manual atau dapat melakukan konfigurasi ulang untuk dapat support Java dan Spring seperti ini.
Sampai tahap ini, kita sudah memiliki entry point ke controller kita yaitu
/hello
. Namun, pada pemanggilan REST API kita pasti akan menentukan HTTP Method apa yang digunakan, misalnya GET
, POST
, PUT
, atau DELETE
, serta mungkin kita memerlukan nested routes. Nah, hal ini kita lakukan melalui functions pada controller kita dengan dianotasikan dengan anotasi seperti @RequestMapping
, @GetMapping
, @PostMapping
, dan lain sebagainya. Pada contoh ini misalnya, kita akan membuat sebuah GET
API, yang akan diarahkan ke route /hello
. Untuk membuatnya, kita cukup menambahkan:@RestController @RequestMapping("hello") public class HelloWorldController { @GetMapping public String index() { return "Hello World!"; } }
Karena route yang ingin kita arahkan untuk sample API ini tidak memiliki nested routes maka kita tidak perlu menambahkan parameter apapun pada anotasi
@GetMapping
. Akan lain halnya apabila fellowdevs ingin membuat API dengan nested route misalnya /hello/hi
. Untuk ini, kita perlu memberikan value "hi"
pada anotasi @GetMapping
kita, misalnya:@RestController @RequestMapping("hello") public class HelloWorldController { @GetMapping public String index() { return "Hello World!"; } @GetMapping("hi") public String hi() { return "Hi World!"; } }
Nah, mengenai anotasi seperti
@GetMapping
, @PostMapping
, etc yang saya sebut-sebut ini sama cara kerjanya dengan @RequestMapping
, hanya saja anotasi ini adalah shortcuts. Kalian dapat mempelajarinya lebih detil di sini.Tanpa fellowdevs sadari, dengan mengikuti langkah-langkah tadi, kita sudah baru saja membuat dua API sederhana yang dapat digunakan. Untuk menggunakan API tersebut, mari kita run dulu aplikasi Spring Boot kita. Cara termudah untuk run project Spring Boot adalah dengan menggunakan tool seperti Maven atau Gradle. Ya, pada dasarnya memang Maven atau Gradle sudah menjadi common tool juga dalam development Java.
Pada project ini kita menggunakan Maven, maka langkah yang kita perlu lakukan untuk run project ini adalah:
- Buka terminal pada IDE kalian masing-masing, pada VSCode bisa dengan menggunakan shortcut
Ctrl + ~
. Atau bisa juga kita menggunakanCMD
atau command prompt apabila fellowdevs lebih terbiasa di Windows.
- Pastikan lokasi folder di terminal saat ini adalah di dalam project kita.
- Run command
mvn clean spring-boot:run
- Berdoa agar tidak error ๐
Here is a screenshot dari IDE saya untuk referensi.
Setelah menjalankan command tadi, tunggu beberapa saat untuk Spring akan memulai keajaibannya. Jangan kaget kalau akan banyak yang tiba-tiba muncul di terminal kita, itu semua adalah log yang dituliskan oleh Spring, ini akan sangat membantu kita untuk mengetahui jika ada yang salah/error. Kalau segala sesuatunya lancar, maka fellowdevs akan mendapatkan informasi seperti ini di log console masing-masing (abaikan apabila ada log lain yang berbeda dengan kalian).
Dari log tersebut, kita dapat mengerti bahwa aplikasi kita sudah berhasil up di atas Tomcat (embedded servlet container-nya) di port
8080
dengan context root/context path /adam
. Context root ini adalah entry point untuk masuk ke aplikasi kita, berbeda dengan anotasi @RestController
atau @Controller
yang merupakan entry point ke API yang kita buat.Nah, sekarang saatnya kita coba API yang kita buat. Cara paling mudah untuk melakukannya adalah dengan menggunakan browser. Since kedua API kita ini menggunakan method
GET
, maka tidak jadi masalah dengan menggunakan browser. Namun untuk method lain seperti POST
misalnya, kita akan membutuhkan tool lain yang memudahkan kita melakukan hit ke API kita tersebut seperti cURL atau Postman.Melalui browser, ketik pada address bar
http://localhost:8080/adam
dan enter. Apa yang dapat kita lihat? Sebuah error page!โ Eits! Jangan putus asa dulu. Kenapa kita dihadapkan dengan error page ini? Informasi yang penting yang bisa kita peroleh melalui halaman ini ada pada baris terakhir, yaitu
Not Found
atau 404
. Kode ini adalah HTTP Status Code dimana kode error yang diawali dengan angka adalah 4
adalah error yang disebabkan oleh client atau dalam hal ini adalah kita yang memanggil. Kode 404
sendiri berarti kita melakukan request terhadap resource yang tidak ada atau tidak ditemukan.Perhatikan URL yang kita hit, yaitu
/adam
, seperti yang saya mention sebelumnya ini adalah context path/context root aplikasi kita, bukan ke controller atau API entry points. Nah, kalau kita lihat lagi ke code kita, kita tidak pernah mendefinisikan API dengan route /adam
bukan? Satu-satunya controller kita yaitu HelloWorldController.java
, kita mapped ke /hello
. Artinya, kita memang tidak punya controller yang meng-handle route ke /adam
, melainkan /adam/hello
.Dengan demikian, kita perlu mengarahkan request kita ke
/adam/hello
instead of /adam
. And voila!Kita sudah berhasil mendapatkan response dari API yang kita buat. Saya harap fellowdevs sudah mulai mengerti bagaimana konsep routing-nya yang terjadi melalui anotasi
@RequestMapping
. Request /adam/hello
akan diarahkan ke controller /hello
di HelloWorldController.java
yakni fungsi index()
. Sedangkan untuk mengarahkan ke API di fungsi hi()
, maka kita cukup arahkan request kita ke /adam/hello/hi
.Perlu diperhatikan bahwa path
/adam
yang saya tambahkan adalah effect dari property server.servlet.context-path
yang saya tambahkan di application.properties
pada contoh project yang saya sediakan. Apabila fellowdevs tidak menggunakan contoh project saya, maka path /adam
ini dapat dihilangkan karena by default Spring tidak menambahkan context root apapun ke dalam aplikasi.Selamat! Karena kalian baru saja berhasil membuat dua API dengan menggunakan Spring Boot! Cukup mudah bukan? Nah, kita akan lanjut dengan case study kita yaitu aplikasi to-do list dengan tiga API yang akan kita buat.
Untuk menghentikan aplikasi, simply close terminal atau ketik
CTRL + C
pada terminal.Setting up Database
Project aplikasi kita akan menggunakan real data yang tersimpan di dalam database PostgreSQL, untuk itu kita perlu melakukan setup database kita terlebih dahulu.
Create Database
Ada beberapa cara untuk membuat database PostgreSQL. Apabila saat instalasi PostgreSQL fellowdevs memilih untuk meng-install juga pgAdmin (aplikasi GUI PostgreSQL) maka kalian dapat menggunakannya untuk membuat database baru. Caranya pun mudah karena sudah tersedia menu-menu yang dapat dipahami.
Namun saya akan menggunakan CLI karena kemungkinan tidak semua fellowdevs pembaca tulisan ini meng-include instalasi pgAdmin.
Untuk membuat database PostgreSQL dengan CLI, kita akan menggunakan terminal atau CMD. Buka CMD dan masuk ke dalam mode
psql
dengan mengetikkan command berikut.psql -U postgres
Di command tersebut saya menambahkan parameter
-U
untuk memilih user database saya. Default PostgreSQL akan menambahkan user postgres
, apabila fellowdevs hendak menggunakan user kalian masing-masing silakan ganti dengan nama user kalian. Pada tahap selanjutnya, kita akan diminta untuk memasukkan password untuk user kita. Password ini kita dapatkan saat kita mendaftarkan user kita, jadi diingat sendiri ya. Untuk case user postgres
, apabila saat instalasi tidak mengisi password, maka dapat dikosongkan.Setelah kita berhasil login, kita akan berada dalam mode
psql
dengan ditandai prefix CLI kita adalah postgres=#
. Untuk membuat database, kita bisa menggunakan command berikut.CREATE DATABASE bcaitworks;
Setelah menjalankan command tersebut, kita dapat mengecek apakah database tersebut sudah sukses terbentuk dengan menggunakan command
\l
dan muncul list database kita.Bisa dilihat bahwa database
bcaitworks
kita sudah terbentuk. Nah biasanya setelah kita membuat database tahap selanjutnya adalah membuat komponen di dalamnya, seperti schema, tables, relations dll.Tapi saya ingin menunjukan kepada fellowdevs keuntungan menggunakan teknologi JPA (tidak hanya spesifik Spring) yang dapat membantu kita dalam membuat komponen tersebut secara otomatis hanya dengan membuat entity class kita. Entity class ini sering juga disebut sebagai model.
Gampangnya entity/model ini adalah representasi tabel-tabel database kita dalam bentuk class atau POJO sehingga kita dapat menggunakannya dengan mudah sebagai object di dalam program kita.
Database Modeling
Sebuah task dalam to-do list, biasanya akan memiliki judul sebagai nama dari task tersebut, serta status mengenai task tersebut apakah sudah selesai/belum. Untuk itu, tabel yang akan menampung data to-do kita perlu ditambahkan fields/kolom berikut ini.
id
adalah kolom identifier untuk setiap record. Nantinya kita akan isi denganUUID
.
- Kolom
title
akan menampung judul dari task.
is_done
akan menampung status dari task.
- Empat kolom lain yaitu
created_by
,modified_by
,created_date
danmodified_date
adalah kolom audit trail. Saya sangat merekomendasikan untuk selalu menambahkan kolom audit trail semacam ini dalam setiap tabel terutama tabel yang ditujukan untuk menyimpan data transaksi aplikasi. Audit trail fields ini akan membantu kita nantinya terutama dalam hal data integrity.
Field
created_by
dan modified_by
akan kita gunakan pada Chapter 3.Nah, kita sudah selesai pada tahap database design/modeling. Selanjutnya, kita akan langsung membuat tabel ini melalui entity class di project kita.
Entity Class
Lanjut di IDE kita, mari buat folder baru untuk menampung entity class kita. Saya akan namakan folder ini dengan nama
model
. Di dalamnya, kita buat class dengan nama Todo.java
. Sekali lagi, class ini akan menggambarkan tabel todo
kita di dalam database.Pada class ini, kita akan membuat fields tadi melalui variables yang ditambahkan anotasi yang menunjukan stereotype dari object tersebut.
package com.bca.adam.model; import java.util.Calendar; import java.util.Date; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Type; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; @Entity @Table(name = "todo") @Data @ToString @EqualsAndHashCode public class Todo { @Id @GeneratedValue(generator = "UUID") @Type(type = "pg-uuid") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") @Column(name = "id", updatable = false, nullable = false) private UUID id; @Column(name = "title") private String title; @Column(name = "created_date") @ColumnDefault("NOW()") private Date createdDate; @Column(name = "modified_date") private Date modifiedDate; @Column(name = "is_done") private boolean isDone; public Todo(String title) { this.title = title; this.createdDate = Calendar.getInstance().getTime(); } }
Penjelasan dari code di atas:
@Entity
adalah anotasi untuk memberi tahu Spring bahwa class ini adalah sebuah entity object. Dalam JPA, entity adalah wajib.
@Table
menerangkan bahwa class ini adalah representasi dengan tabel di database dengan namatodo
.
@Data
,@ToString
,@EqualsAndHasCode
adalah anotasi dari Lombok. Library ini memudahkan kita terutama dalam membuat aksesor mutator sebuah object. Selebihnya tentang Lombok dapat dipelajari di sini.
@Id
adalah anotasi untuk menunjukan ID atau dapat juga dianalogikan sebagai primary key (PK) pada entity. Entity wajib memiliki ID.
- Karena ID ini akan kita generate dengan format
UUID
, maka kita perlu men-define generatornya. Dalam hal ini, kita anotasikan dengan@GeneratedValue
,@Type
serta@GenericGenerator
seperti pada code di atas.
- Sama halnya seperti
@Table
,@Column
mendefinisikan bahwa field Java object ini akan menjadi field / kolom pada tabel di database.
@ColumnDefault
digunakan untuk mendefinisikan default value suatu kolom.
Sejauh ini kita sudah membuat database untuk digunakan, serta men-design struktur database dan tabel kita. Namun, kita belum menghubungkan project kita dengan database yang kita buat tadi. Di sinilah akan kita gunakan file
application.properties
pada folder /resources
. Seperti yang saya sampaikan di awal, file ini biasanya menampung konfigurasi dari aplikasi kita. Koneksi database termasuk konfigurasi sehingga kita dapat letakan di dalam file ini.Untuk menambahkan konfigurasi koneksi database, kita dapat menggunakan property berikut ini.
spring.datasource.url= jdbc:postgresql://localhost:5432/bcaitworks spring.datasource.username= postgres spring.datasource.password= admin spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect # Hibernate ddl auto (create, create-drop, validate, update) spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
- Yang pertama kita perlu definisikan URL database kita dalam ekspresi datasource. Karena kita bekerja di
localhost
dan default port PostgreSQL adalah5432
, maka koneksi host kita adalahlocalhost:5432/{database_name}
.{database_name}
diganti dengan nama database masing-masing.
- Kemudian, kita juga definisikan credentials untuk koneksi database tersebut.
- Karena banyak varian database yang ada dan library Spring JPA dibuat se-generic mungkin, maka kita perlu secara spesifik mendefinisikan jenis database yang kita pakai. Hal ini akan berpengaruh pada dialect / query generation yang digunakan saat melakukan operasi ke database kita sesuai dengan varian yang digunakan. Dalam hal ini kita gunakan dialect dari PostgreSQL.
- Nah untuk memanfaatkan fitur auto generation object database melalui aplikasi kita, kita perlu menambahkan property
spring.jpa.hibernate.ddl-auto
. Property ini memberi tahu Spring bahwa kita ingin Spring JPA untuk membuatkan object database secara otomatis saat aplikasi up pertama kali. Untuk valuenya, kita isi denganupdate
saja.
spring.jpa.hibernate.ddl-auto
dapat juga diisi dengan create
, create-drop
, atau validate
. create
akan melakukan auto create object database apabila belum ada. create-drop
akan melakukan drop keseluruhan database terlebih dahulu, lalu men-generate ulang (fresh). update
akan melakukan modifikasi terhadap object database sesuai dengan perubahan yang dilakukan pada entity.Saya sangat merekomendasikan untuk tidak mengaktifkan fitur ini pada environment production atau untuk dev purpose saja dikarenakan auto generation ini dapat berakibat fatal bila tidak dimengerti dengan baik. Bayangkan apabila database kita sudah digunakan dan berisi data-data aplikasi yang penting, kemudian tanpa sengaja kita men-deploy ulang aplikasi dengan posisi fitur ini dinyalakan dan valuenya
create-drop
๐คฏ.spring.jpa.show-sql
adalah opsi apabila kita ingin menampilkan query yang di-generate oleh JPA saat aplikasi dijalankan. Karena ini akan berpengaruh pada logging system aplikasi dan sangat verbose, aktifkan hanya untuk dev purpose saja.
Setelah selesai dengan konfigurasi tersebut, maka saatnya untuk kita menjalankan aplikasi kita lagi agar dapat men-generate object database kita secara otomatis. Untuk menjalankannya dapat kita run kembali command
mvn clean spring-boot:run
.Dan apabila berjalan dengan lancar, maka kita akan mendapatkan tabel kita telah dibuat secara otomatis lengkap dengan kolom dan tipe datanya. Nice๐ bukan ? Kita bisa mengecek hal ini melalui CLI tadi, yaitu dengan command berikut:
postgres=# \c bcaitworks You are now connected to database "bcaitworks" as user "postgres". bcaitworks=# \d List of relations Schema | Name | Type | Owner --------+------+-------+---------- public | todo | table | postgres (1 row) bcaitworks=# \d todo Table "public.todo" Column | Type | Collation | Nullable | Default ---------------+-----------------------------+-----------+----------+--------- id | uuid | | not null | created_date | timestamp without time zone | | | now() is_done | boolean | | not null | false modified_date | timestamp without time zone | | | title | character varying(255) | | not null | created_by | character varying(255) | | | modified_by | character varying(255) | | | Indexes: "todo_pkey" PRIMARY KEY, btree (id)
Sampai tahap ini database kita sudah selesai ๐. Selanjutnya kita dapat memulai untuk membuat API dengan operasi ke database ini.
Project's APIs development
Untuk kerapian code kita seperti halnya folder
controller
dan model
, kita akan membuat dua folder tambahan yaitu repository
dan service
. Pembagian folder ini juga ditujukan untuk pembagian fungsi object Java kita nanti dan sudah sangat common dalam arsitektur project seperti ini. repository
akan berfungsi sebagai implementasi JPA dan mengandung logic yang berhubungan dengan operasi database. Sedangkan service
adalah dimana kita menempatkan business logic yang lebih luas.Sederhananya, alur program kita akan berjalan seperti ini.
Setelah membuat kedua folder tersebut, struktur folder project kita akan menjadi seperti ini.
. โโโ src โ โโโ main โ โ โโโ java โ โ โ โโโ com/bca/adam โ โ โ โโโ model โ โ โ โโโ repository โ โ โ โโโ controller โ โ โ โโโ service โ โ โโโ resources
Berdasarkan penjelasan sebelumnya, ada 4 endpoints API yang akan kita buat. Personally, saya merasa lebih mudah untuk membuat API yang sifatnya inquiry atau menampilkan data terlebih dahulu dibanding API untuk menambah atau mengubah data. Untuk itu, kita akan terlebih dahulu membuat API untuk mendapatkan daftar to-do.
Get to-do list
Untuk memulai membuat API, lebih dahulu kita akan membuat operasi database yang diperlukan. Untuk mendapatkan list record data to-do, SQL query yang kemungkinan kita akan gunakan adalah
SELECT * FROM todo;
. Dengan bantuan JPA, kita tidak perlu lagi membuat query seperti ini. Di dalam folder repository
, kita buat interface
TodoRepository.java
, bukan class
ya. Kenapa bukan class
? Karena kita akan memanfaatkan JPA yang sudah disediakan oleh Spring JPA yaitu JpaRepository
yang adalah interface
.package com.bca.adam.repository; import java.util.List; import java.util.Optional; import java.util.UUID; import com.bca.adam.model.Todo; import org.springframework.data.jpa.repository.JpaRepository; public interface TodoRepository extends JpaRepository<Todo, String> { //get todo by ID Optional<Todo> findById(UUID id); //get all todo, similar with "SELECT * FROM todo;" //this actually included in JpaRepository //it is written here just to avoid confusion for beginners List<Todo> findAll(); }
Pada interface ini, kita akan membuat dua operasi database, yaitu mendapatkan semua list to-do dan mendapatkan spesifik task to-do berdasarkan
ID
. Tujuan kedua fungsi ini akan lebih jelas fungsinya saat nanti kita buat UI nya di Chapter 2.Sebenarnya, kita bahkan tidak perlu menambahkan fungsi apapun di interface ini karena
JpaRepository
sudah memiliki operasi database yang cukup lengkap. Daftar operasi yang sudah disediakan dapat fellowdevs lihat pada definisi interface JpaRepository
.Derived Query Method
Apabila kita tetap membutuhkan operasi database yang tidak ada dalam interface tersebut, kita dapat dengan mudah membuatnya melalui penamaan fungsi yang semantic. Strategi Spring JPA ini disebut juga dengan Derived Query Method yang memudahkan kita dalam membuat query berdasarkan nama fungsi.
Misalnya
findAll()
, tanpa mengetahui apa implementasinya dari fungsi ini, kita sudah langsung dapat mengerti bahwa fungsi ini bermakna "dapatkan semua data" dari sebuah entity. Saya sering menyebut hal ini sebagai magic-nya Spring JPA.Untuk lebih mengerti mengenai bagaimana membuat custom query yang lebih kompleks dengan derived query methods dan memahami bagaimana cara kerjanya, fellowdevs dapat membaca artikel ini.
Selanjutnya, kita buat class baru di dalam folder
service
, dengan nama TodoService.java
. Di dalam class ini, biasanya akan mengandung banyak logic yang berhubungan dengan business logic atau bahkan data manipulation. Tetapi karena saat ini kita belum membutuhkan hal tersebut, maka class ini dapat dibilang hanya sebagai perantara dari controller
ke repository
saja.package com.bca.adam.service; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.UUID; import com.bca.adam.model.Todo; import com.bca.adam.repository.TodoRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; @Service @Slf4j public class TodoService { @Autowired TodoRepository todoRepo; public List<Todo> getAll() { List<Todo> todos = new ArrayList<>(); todoRepo.findAll().forEach(todos::add); if (todos.isEmpty()) { log.info("You have no todos!"); return new ArrayList<>(); } return todos; } }
@Service
adalah anotasi untuk memberi tahu Spring bahwa class ini adalah sebuah component penting, yaitu Service.
@Slf4j
kita tambahkan untuk menambahkan fungsi logging pada aplikasi kita secara otomatis dan mudah. Anotasi ini kita dapatkan dari library Lombok yang kita tambahkan padapom.xml
. Bayangkan kalau kita tidak menggunakan Lombok, akan lebih banyak lagi memakan waktu dan langkah untuk sekedar membuat fungsi logging.
@Autowired
adalah anotasi yang sangat penting dalam Spring. Salah satu keunggulan Spring adalah IoC (Inversion of Control) atau sering juga disebut Dependency Injection (DI). Anotasi@Autowired
memungkinkan kita dengan mudah untuk melakukannya.
Konsep Dependency Injection sangatlah penting untuk fellowdevs mengerti dan pahami, tidak hanya dalam Spring atau Java namun konsep pemrograman secara umum. Memahami konsep ini akan sangat membantu kalian di kemudian hari dan saya sangat merekomendasikan fellowdevs untuk mempelajarinya. Silakan dapat kalian pelajari lebih lanjut di sini.
- Sisa code lainnya adalah OOP biasa, yaitu memanggil fungsi dari
TodoRepository
, melakukan validasi kosong/tidak, kemudian mengembalikannya sebagai return value fungsigetAll()
.
Service dan repository kita sudah selesai, selanjutnya kita akan membuat controller-nya. Tentu fellowdevs sudah mengerti ya konsep controller melalui penjelasan sebelumnya. Di dalam folder
controller
, kita tambahkan class baru yaitu TodoController.java
.package com.bca.adam.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.bca.adam.model.Todo; import com.bca.adam.service.TodoService; import io.swagger.annotations.Api; import lombok.extern.slf4j.Slf4j; @CrossOrigin(origins = "*") @RestController @RequestMapping("/todo") @Slf4j public class TodoController { @Autowired TodoService todoService; @GetMapping("") public ResponseEntity<List<Todo>> getAll() { log.info("Getting todo list...") try { List<Todo> todos = todoService.getAll(); if (todos.isEmpty()) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } return new ResponseEntity<>(todos, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null); } } }
- Request menuju ke
TodoController
kita arahkan menggunakan path/todo
melalui anotasi@RequestMapping("/todo")
@CrossOrigin
ditambahkan untuk mencegah error CORS.
Di sini saya memberikan anotasi
@CrossOrigin
dengan value *
, tapi pada project sebenarnya fellowdevs harus menggantinya dengan eligible client kalian ya!That's it! Sekarang mari kita test API yang sudah kita buat ini. Kali ini saya akan menggunakan Postman. Jangan lupa start aplikasi kita dan masukkan URL aplikasi kita ke path controller inquiry list todo kita yaitu
localhost:8080/adam/todo/
.Response yang akan kita dapatkan adalah .... kosong! Hmmm, apakah program kita error? Mari kita inspeksi. HTTP status yang kita dapatkan adalah
204 No Content
, dimana kalau kita cek ke controller, kita memang mengembalikan HttpStatus.NO_CONTENT
apabila tidak ada data. Kemudian kita juga bisa mengecek ke logging kita, bahwa kita dapatkan log:INFO 12240 --- [nio-8080-exec-3] com.bca.adam.service.TodoService : You have no todos!
Ya! Kita juga membuatnya di service kita untuk menuliskan log tersebut apabila tidak ada data ditemukan. Artinya tidak ada yang error dengan program kita. Setelah diingat-ingat, kita memang belum menambahkan data apapun ke dalam database kita tadi. Nah untuk sementara, mari kita tambahkan melalui direct query ke dalam database.
INSERT INTO public.todo (id,title) VALUES ('e8e40842-eb4d-4d0c-8d55-f5124c3051fa'::uuid,'Cuci baju');
UUID saya dapatkan dengan menggunakan generator online.
Setelah kita tambahkan data tersebut ke dalam database kita dan hit ulang API kita, maka kita akan mendapatkan response seperti ini.
[ { "id": "e8e40842-eb4d-4d0c-8d55-f5124c3051fa", "title": "Cuci baju", "createdDate": "2023-02-10T10:00:21.527+00:00", "modifiedDate": null, "done": false } ]
Nice! Sekarang API get to-do list kita sudah selesai.
Add a to-do item
Selanjutnya kita akan membuat API untuk menambahkan data to-do. Seperti yang sudah saya sebutkan sebelumnya bahwa Spring JPA sudah menyediakan untuk kita fungsi database dasar yaitu CRUD. Maka dari itu, kita tidak akan menambahkan fungsi ini secara manual pada repository kita melainkan kita cukup menambahkan fungsi pada service kita yang akan memanggil fungsi
save()
yang telah disediakan Spring tersebut.// .... previous lines public Todo addTodo(Todo todo) { return todoRepo.save(new Todo(todo.getTitle())); }
Sedikit penjelasan mengenai code ini adalah:
- Fungsi
addTodo
akan menerima parameter berupa object classTodo
. Karena object ini sudah serupa dengan generic type yang kita tentukan saat mengimplementasikan JPA yakniJpaRepository<Todo, String>
, maka Spring akan dengan mudah memproses object ini untuk di-map ke dalam entity.
- Fungsi
save
yang dipanggil melaluitodoRepo
adalah bawaan dariJpaRepository
.
- Untuk tahap ini, kita hanya akan mengambil field
title
saja dari parameter yang diterima. Oleh karena itu saya membuat new object dengan menggunakan constructor yang sudah saya definisikan pada classTodo
sebelumnya.
Langkah selanjutnya yaitu membuat controller untuk API
addTodo
ini.// ... previous lines /* * basically you might want to use DTO object to store the request and response * payload instead of the model itself */ @PostMapping("") public ResponseEntity<Todo> addTodo(@RequestBody Todo todo) { try { if (todo.getTitle() == null || !StringUtils.hasText(todo.getTitle())) { log.info("To add item to your todo list, you must give it a title!"); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity<>(todoService.addTodo(todo), HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null); } }
Tidak ada yang baru di baris code ini selain penggunaan anotasi
@RequestBody
. Anotasi ini kita gunakan untuk menerima request body yang dikirimkan oleh client. Apabila kalian belum mengerti apa itu request body, bisa pelajari dulu tentang konsep API ya.Parameter yang kita terima sebagai request body adalah object
Todo
. Nah, saya beri bocoran sedikit. Pada dasarnya proses pertukaran data yang terjadi pada API kita akan menggunakan format JSON. Tidak terkecuali dalam mengirimkan request body ini. Nah pertanyaannya, bagaimana mungkin JSON yang dikirimkan tersebut dapat diterima sebagai object Todo
yang notabene adalah Java class? Di sinilah salah satu kemudahan yang diberikan oleh Spring.Melalui anotasi
@RequestBody
, JSON string tersebut akan secara otomatis di-deserialize-kan untuk dapat dikonversi menjadi object Todo
. Salah satu syaratnya adalah dengan menggunakan nama field yang sama. Apabila ada field yang tidak sama dengan object yang diminta, pilihannya tergantung pada kita. Apakah kita akan tetap mengolah request tersebut atau return error. Untuk menunjukannya pada fellowdevs saya akan menambahkan field tambahan yang tidak ada pada object Todo
nanti.Untuk cara yang lebih "proper", kita juga dapat menggunakan model DTO (Data Transfer Object), namun untuk mempersingkat tulisan ini saya tidak membahasnya di sini. Fellowdevs dapat membaca mengenai DTO di sini.
{ "title": "Kerjain tugas BCAITWORKS!", "unmapped_field": "i'm not gonna be mapped!" }
Jangan lupa untuk mengubah request method-nya menjadi
POST
ya. Dan apabila kita send request ini, maka kita akan mendapatkan response seperti berikut.Nah, dapat fellowdevs lihat sendiri bahwa request tersebut berhasil terkirim dan data to-do berhasil ditambahkan. Untuk mengeceknya, kita dapat memanggil API get to-do sebelumnya atau inquiry ke database langsung.
// returned todo list after adding data [ { "id": "e8e40842-eb4d-4d0c-8d55-f5124c3051fa", "title": "Cuci baju", "createdDate": "2023-02-10T10:00:21.527+00:00", "modifiedDate": null, "done": false }, { "id": "ab7ec7ce-e159-4e53-8647-edb555281876", "title": "Kerjain tugas BCAITWORKS!", "createdDate": "2023-02-11T18:39:22.598+00:00", "modifiedDate": null, "done": false } ]
Ingat bahwa kita menambahkan field
unmapped_field
yang tidak ada pada object Todo
. Seperti yang saya bilang pilihannya kembali ke kita sebagai developer. Apakah kita akan menolak request ini karena mengandung unsur yang tidak kita kenal? Atau akan kita biarkan namun tidak diproses? Pilihannya bermacam-macam. Dalam hal ini, saya hanya mengambil field title
dan melakukan validasi terhadapnya. Silakan fellowdevs dapat memodifikasi code tersebut apabila ingin melakukan hal yang berbeda, anggap saja belajar ๐Pada API
addTodo
, kita melakukan validasi blank/kosong terhadap field title
dan mengembalikan error. Silakan dapat fellowdevs coba ya!Update items status
Nah selanjutnya kita akan membuat API kita yang terakhir yaitu fungsi untuk mengubah data to-do. Dalam hal ini, data yang akan diubah adalah status dari sebuah data to-do yaitu misalnya sebuah to-do sudah selesai dikerjakan, maka kita cukup meng-update statusnya menjadi Done. Lalu apabila to-do tersebut hendak dikerjakan kembali maka user dapat mengubah menjadi Undone.
Kita akan menggunakan kembali fungsi
save
dari JpaRepository
. Meskipun fungsi save
ini kita tadi gunakan untuk membuat data baru, namun dapat juga kita gunakan untuk meng-update sebuah data. Spring JPA sudah cukup cerdas untuk membedakan apakah operasi yang ingin kita lakukan adalah insert atau update. Untuk meng-update data, kita perlu men-set terlebih dahulu data yang ingin diubah, biasanya kita akan lakukan terlebih dahulu inquiry seperti misalnya findById(id)
. Data yang kita dapatkan dari operasi ini cukup kita set field mana yang ingin diubah. Syarat utamanya, field ID
yang diberi anotasi @Id
pada entity class harus terisi (tidak null) maka Spring JPA akan menganggap operasi tersebut adalah operasi update.// ... previous lines public Todo modifyTodo(String id) { Optional<Todo> todo = todoRepo.findById(UUID.fromString(id)); if (todo.isPresent()) { Todo existTodo = todo.get(); existTodo.setDone(!existTodo.isDone()); existTodo.setModifiedDate(new Date()); return todoRepo.save(existTodo); } else { throw new IllegalArgumentException(String.format("data with id %s not found.", id)); } }
Seperti yang kalian lihat, sebelum mengubah data saya terlebih dahulu mengambil data berdasarkan
ID
. Kemudian apabila data dengan ID
tersebut ditemukan, maka saya akan gunakan sebagai object dan set field isDone
dan modifiedDate
untuk diubah, barulah saya panggil fungsi save
dari JpaRepository
. Sedangkan apabila data dengan ID
tersebut tidak ditemukan, maka saya akan mengembalikan error karena tidak dapat mengubah data yang tidak ditemukan.// ... previous lines @PutMapping("") public ResponseEntity<Todo> modifyTodo(@RequestBody Map<String, String> body) { try { if (body.get("id") == null) return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(todoService.modifyTodo(body.get("id")), HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null); } }
Kemudian kita tambahkan code di atas pada
TodoController.java
. Untuk melakukan update data, biasanya digunakan HTTP method PUT
atau PATCH
. Pada contoh ini saya akan gunakan PUT
saja.Kalau fellowdevs perhatikan, parameter pada
@RequestBody
yang diterima saya tidak lagi menggunakan object Todo
seperti pada API addTodo
melainkan menggunakan tipe data Map<String, String>
. Hal ini saya maksudkan untuk menunjukan kepada fellowdevs bahwa kita juga dapat menggunakan jalur "konvensional" semacam ini. Namun kekurangannya, kita harus mengolah object Map
ini secara manual apabila ingin digunakan atau bahkan dikonversi ke object seperti entity atau DTO.Save semua file tersebut dan kita tes API ini. Karena kita memerlukan
ID
untuk dapat mengubah data, maka untuk saat ini fellowdevs dapat mengambil ID
tersebut dari existing data di database terlebih dahulu. Misalnya saya ingin mengubah data ini:{ "id": "ab7ec7ce-e159-4e53-8647-edb555281876", "title": "Kerjain tugas BCAITWORKS!", "createdDate": "2023-02-11T18:39:22.598+00:00", "modifiedDate": null, "done": false }
Maka saya gunakan
ID
tersebut dan mengirimkannya pada request body API modifyTodo
.Setelah kita send, maka status data to-do dengan
ID
tersebut akan berubah menjadi Done (done= true
). Sedangkan apabila saya resend request yang sama, status data ini akan kembali menjadi Undone (done= false
). Hal ini sesuai dengan spesifikasi API yang saya buat untuk contoh sederhana ini.{ "id": "ab7ec7ce-e159-4e53-8647-edb555281876", "title": "Kerjain tugas BCAITWORKS!", "createdDate": "2023-02-11T18:39:22.598+00:00", "modifiedDate": "2023-02-12T06:24:00.989+00:00", "done": true // this switches to false and true everytime the call happen according to current status }
Nah, setelah semua tes kita berhasil dan tidak ada error, maka API untuk aplikasi yang akan kita bangun sudah selesai ๐!
ย
Untuk source code dari project yang kita kerjakan dapat fellowdevs checkout melalui Git repository ini.
What's Next?
Setelah kita membuat API untuk aplikasi to-do sederhana kita ini, tahapan selanjutnya adalah membuat aplikasi frontend yang menampilkan UI agar aplikasi kita dapat digunakan. Yuk, lanjut ke Chapter 2! ๐
๐ Bonus!
API Delivery with Swagger
Nah, setelah kita membuat API aplikasi kita, tahap selanjutnya adalah mendokumentasikan API tersebut menjadi sebuah dokumen yang dapat dikonsumsi baik itu bagi frontend developer, next developer, atau bahkan kita sendiri sebagai sumber informasi/landasan spesifikasi API tersebut dibuat.
Apabila pembagian tim kerja kita terpisah menjadi backend dan frontend developer, maka kita perlu men-deliver dokumen tersebut kepada frontend developer supaya mereka dapat mengetahui bagaimana menggunakan API yang sudah kita buat.
Bagaimana caranya? Paling mudahnya, kita dapat membuat dokumen dalam bentuk text seperti Word atau Excel dan menjelaskan tentang apa saja yang dibutuhkan untuk dapat menggunakan API kita. Nah persoalannya, membuat dokumentasi semacam ini adalah pekerjaan yang membosankan. Apalagi kalau API kita sering mengalami perubahan, maka kita juga akan berulang kali membuat dokumen semacam ini.
Nah, kabar baik bagi kita semua, Swagger hadir untuk membantu kita dalam hal ini. Dengan Swagger, kita dapat memiliki dokumentasi API yang dengan mudah di-generate sesuai dengan code kita, lengkap, tampilan yang bagus dan ter-standar, serta interaktif! Interaktif yang saya maksud adalah bahkan kita bisa mencoba endpoints API dengan klik. Hal ini memungkinkan penerima dokumentasi dapat langsung melakukan verifikasi dengan cepat (rapid testing) apakah API yang kita sediakan dapat mereka pergunakan atau tidak. Hebat bukan?
Saya akan bahas secara terpisah mengenai bagaimana cara kita dapat mengintegrasikan Swagger ke project kita ini. Silakan fellowdevs dapat membaca pada artikel ini.