Restful API dengan Aqueduct

Cara membuat CRUD API dengan Aqueduct

Yudi Setiawan
Nusanet Developers

--

Sebagai Frontend developer terkadang pernah nggak sih kita penasaran dengan kode yang dikerjakan oleh Backend developer. Jadi, bagi kamu semua yang pernah merasakan hal tersebut saya juga merasakannya. Awal rasa penasaran ini muncul ketika saya mulai berpikir apakah bahasa pemrograman Dart yang saya pakai setiap hari ini bisa dipakai untuk sisi Backend. Lalu, berawal dari rasa penasaran akhirnya mulailah action untuk mulai searching di Google dan akhirnya ketemu.

Hasil pencarian di Google

Dari hasil pencarian tersebut ternyata saya menemukan beberapa referensi yang bagus. Dan hasilnya saya menemukan Dart Framework Server Side yang bernama Aqueduct. Website resminya bisa kamu kunjungi di link berikut ya.

Pengenalan Aqueduct

Dokumentasi mengenai penggunaan Aqueduct menurut saya benar-benar lengkap. Mulai dari awal sampai akhir sudah mereka tulis dengan baik. Dan yang membuat saya lebih tertarik lagi ialah bahwa didalam paket Aqueduct ini sudah include dengan dukungan database PostgreSQL dan OAuth2. Berhubung memang ketika pakai Spring Boot dulu saya pakai PostgreSQL jadi hal ini akan semakin mempermudah saya dengan Aqueduct karena sudah terbiasa dengan PostgreSQL.

Itu tadi sedikit pengenalan singkatnya mengenai Aqueduct. Sebelum kita mulai menggunakan Aqueduct ada beberapa hal yang harus kamu siapkan untuk kamu install di lokalmu yaitu sebagai berikut.

  1. Dart
  2. PostgreSQL
  3. VS Code/Android Studio/IntelliJ IDEA

Langkah selanjutnya adalah silakan install Aqueduct-nya dengan mengeksekusi perintah berikut.

pub global activate aqueduct

Untuk memastikan apakah Aqueduct sudah berhasil di-install silakan ketik perintah berikut.

aqueduct

Nantinya output dari perintah tersebut akan menghasilkan keterangan seperti gambar berikut.

Aqueduct berhasil diinstall

Contoh Sederhana

Sekarang mari langsung saja kita akan membuat contoh sederhananya untuk membuat API dengan Aqueduct.

Buat Project

Untuk membuat projeknya silakan ketik perintah berikut.

aqueduct create contoh_sederhana

Perintah diatas akan menghasilkan sebuah direktori dengan nama contoh_sederhana. Buka direktori tersebut menggunakan VS Code/Android Studio/IntelliJ IDEA. Berikut adalah gambaran struktur projeknya.

Struktur projek contoh_sederhana

Buat Endpoint CRUD

Sekarang kita akan membuat endpoint GET, POST, PUT, dan DELETE. Silakan buka file channel.dart dan didalamnya sudah berisikan kode kurang lebih seperti berikut.

import 'contoh_sederhana.dart';

/// This type initializes an application.
///
/// Override methods in this class to set up routes and initialize services like
/// database connections. See http://aqueduct.io/docs/http/channel/.
class ContohSederhanaChannel extends ApplicationChannel {
/// Initialize services in this method.
///
/// Implement this method to initialize services, read values from [options]
/// and any other initialization required before constructing [entryPoint].
///
/// This method is invoked prior to [entryPoint] being accessed.
@override
Future prepare() async {
logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));
}

/// Construct the request channel.
///
/// Return an instance of some [Controller] that will be the initial receiver
/// of all [Request]s.
///
/// This method is invoked after [prepare].
@override
Controller get entryPoint {
final router = Router();

// Prefer to use `link` instead of `linkFunction`.
// See: https://aqueduct.io/docs/http/request_controller/
router
.route("/example")
.linkFunction((request) async {
return Response.ok({"key": "value"});
});

return router;
}
}

Untuk menambahkan endpoint-nya bisa kamu lihat didalam method entryPoint dimana, didalamnya ada objek dengan nama router . Objek router inilah yang berfungsi untuk mendeklarasikan endpoint yang akan kita buat. Pada kode diatas sudah ada satu endpoint yang sudah terbuat ketika kita membuat projeknya yaitu endpoint /example. Endpoint /example ini akan mengembalikan respon JSON berikut.

{
"key": "value"
}

Lalu, untuk menambahkan endpoint-nya kita bisa ubah kode didalam method entryPoint menjadi seperti berikut.

...
@override
Controller get entryPoint {
final router = Router();

// Prefer to use `link` instead of `linkFunction`.
// See: https://aqueduct.io/docs/http/request_controller/
router
.route("/example")
.linkFunction((request) async {
switch (request.method) {
case 'GET':
return Response.ok({'data': 'Endpoint GET berhasil'});
case 'POST':
return Response.created('1', body: {'data': 'Endpoint POST berhasil'});
case 'PUT':
return Response.ok({'data': 'Endpoint PUT berhasil'});
case 'DELETE':
return Response.ok({'data': 'Endpoint DELETE berhasil'});
default:
return Response.badRequest();
}
});

return router;
}
...

Jalankan Server

Untuk menjalankan server-nya kita gunakan perintah berikut.

aqueduct serve
Jalankan server

Secara default server kita berjalan diport 8888 dan konfigurasi ini bisa kamu lihat didalam file main.dart yang isi file-nya kurang lebih seperti berikut.

import 'package:contoh_sederhana/contoh_sederhana.dart';

Future main() async {
final app = Application<ContohSederhanaChannel>()
..options.configurationFilePath = "config.yaml"
..options.port = 8888;

final count = Platform.numberOfProcessors ~/ 2;
await app.start(numberOfInstances: count > 0 ? count : 1);

print("Application started on port: ${app.options.port}.");
print("Use Ctrl-C (SIGINT) to stop running the application.");
}

Sekarang kita test apakah endpoint yang sudah kita buat tadi sudah berjalan. Caranya silakan buka aplikasi sejenis Postman atau Insomnia. Kemudian, masukkan link endpoint-nya.

GET

Test endpoint GET

POST

Test endpoint POST

PUT

Test endpoint PUT

DELETE

Test endpoint DELETE

Bisa kamu lihat bahwa endpoint GET, POST, PUT, dan DELETE yang sudah kita buat tadi sudah berjalan dengan baik. Gimana kesan pertamanya gampang kan membuat endpoint API. Sekarang mari kita ke langkah berikutnya.

Controller

Controller merupakan sebuah class yang berfungsi untuk meng-handle request yang masuk. Class controller ini memiliki fungsi sebagai middleware dimana nantinya dia bisa berada ditengah-tengah ketika sebuah request masuk. Contoh dari fungsi ini ialah misalkan kita mau setiap request yang masuk harus memiliki header x-secret-key:secret . Untuk mengimplementasikannya silakan buat file baru dengan nama my_authorizer.dart lalu isi dengan kode berikut.

import 'contoh_sederhana.dart';

class MyAuthorizer extends Controller {
@override
FutureOr<RequestOrResponse> handle(Request request) {
if (request.raw.headers.value('x-secret-key') == 'secret') {
return request;
}
return Response.badRequest();
}
}

Kemudian, kita buka kembali file channel.dart dan ubah kode didalam method entryPoint menjadi seperti berikut.

@override
Controller get entryPoint {
final router = Router();

// Prefer to use `link` instead of `linkFunction`.
// See: https://aqueduct.io/docs/http/request_controller/
router.route("/example")
.link(() => MyAuthorizer())
.linkFunction((request) async {
switch (request.method) {
case 'GET':
return Response.ok({'data': 'Endpoint GET berhasil'});
case 'POST':
return Response.created('1', body: {'data': 'Endpoint POST berhasil'});
case 'PUT':
return Response.ok({'data': 'Endpoint PUT berhasil'});
case 'DELETE':
return Response.ok({'data': 'Endpoint DELETE berhasil'});
default:
return Response.badRequest();
}
});

return router;
}

Sekarang coba jalankan kembali server-nya dan test untuk setiap request GET, POST, PUT, dan DELETE.

Request GET gagal

Kenapa request GET tersebut gagal? Karena, tadi kita sudah menambahkan validation bahwa setiap request yang masuk harus ada header x-secret-key:secret . Sekarang silakan kita tambahkan header tersebut disetiap request GET, POST, PUT, dan DELETE ya.

Request GET berhasil

Resource Controller

Resource controller merupakan class yang memiliki fungsi hampir mirip seperti controller. Yang membedakannya adalah class resource controller ini hanya bisa mengembalikan respon dan tidak bisa mengembalikan request. Berbeda seperti class controller yang bisa mengembalikan respon dan request. Karena resource controller hanya bisa mengembalikan respon maka, fungsinya lebih cocok untuk membuat endpoint. Sekarang mari kita contohkan untuk memindahkan endpoint-endpoint yang sudah kita deklarasikan di method entryPoint tadi kedalam bentuk class resource controller. Silakan buat file baru dengan nama example_controller.dart dan isikan dengan kode berikut.

import 'package:contoh_sederhana/contoh_sederhana.dart';

class ExampleController extends ResourceController {
@Operation.get()
Future<Response> getExample() async {
return Response.ok({'data': 'Endpoint GET berhasil'});
}

@Operation.post()
Future<Response> postExample() async {
return Response.created('1', body: {'data': 'Endpoint POST berhasil'});
}

@Operation.put()
Future<Response> putExample() async {
return Response.ok({'data': 'Endpoint PUT berhasil'});
}

@Operation.delete()
Future<Response> deleteExample() async {
return Response.ok({'data': 'Endpoint DELETE berhasil'});
}
}

Kemudian, kita buka kembali file channel.dart dan ubah kode didalam method entryPoint menjadi seperti berikut.

Controller get entryPoint {
final router = Router();

// Prefer to use `link` instead of `linkFunction`.
// See: https://aqueduct.io/docs/http/request_controller/
router.route("/example")
.link(() => MyAuthorizer())
.link(() => ExampleController());

return router;
}

Sekarang coba kita test lagi untuk mengakses endpoint GET, POST, PUT, dan DELETE. Seharusnya berhasil ya. Karena pada kode diatas yang kita ubah hanyalah memindahkan fungsi endpoint example kedalam bentuk class resource controller.

PostgreSQL

Sekarang mari kita main-main mengenai database-nya. Seperti yang sudah saya jelaskan diawal tadi bahwa Aqueduct sudah include dengan PostgreSQL. Jadi, untuk konfigurasinya ke database PostgreSQL lebih mudah.

Untuk memulainya kita perlu membuat database baru di PostgreSQL dengan nama tutorial_aqueduct. Saya harap disini kita semua sudah memasang PostgreSQL dilokal-nya masing-masing. Untuk cara instalasi PostgreSQL-nya tidak akan saya jelaskan ditulisan ini ya. 😉

Berikut adalah langkah-langkah untuk membuat database di PostgreSQL.

  • Buka aplikasi command line seperti terminal atau sejenisnya.
  • Pastikan terlebih dahulu untuk menjalankan PostgreSQL-nya.
PostgreSQL sudah berjalan
  • Kemudian, pada aplikasi command line ketikkan perintah psql maka, sekarang kita sudah masuk ke command line PostgreSQL.
Command line PostgreSQL
  • Sekarang kita buat database-nya dengan menggunakan perintah berikut.
create database tutorial_aqueduct;

Maka, nantinya akan terbentuk sebuah database baru dengan nama tutorial_aqueduct.

Database tutorial_aqueduct sudah terbentuk

Update Pubspec.yaml

Silakan kita buka file pubspec.yaml dan ubah kode didalamnya menjadi seperti berikut.

name: contoh_sederhana
description: An empty Aqueduct application.
version: 0.0.1
author: stable|kernel <jobs@stablekernel.com>

environment:
sdk: ">=2.0.0 <3.0.0"

dependencies:
aqueduct: 3.3.0+1


dev_dependencies:
test: ^1.0.0
aqueduct_test: ^1.0.0
pedantic: ^1.9.0


dependency_overrides:
postgres: ^2.1.1

Kenapa file pubspec.yaml-nya harus kita ubah? Karena hal tersebut berhubungan dengan isu ini.

Buat Table Pengguna

Sekarang kita akan membuat tabel baru dengan nama pengguna. Untuk membuat tabel-nya kita tidak akan langsung meng-eksekusi query SQL-nya di command line PostgreSQL melainkan melalui ORM. Apa itu ORM? ORM adalah singkatan dari Object Relationship Mapping yang berfungsi untuk melakukan mapping data dari bahasa pemrograman kedalam tabel didatabase.

https://www.codepolitan.com/mengapa-menggunakan-orm-dalam-membuat-aplikasi-android-59d5a3928887e

Sekarang mari kita buat file baru dengan nama pengguna.dart dimana, file ini nantinya akan di-mapping-kan menjadi sebuah tabel dengan nama pengguna didatabase.

import 'package:contoh_sederhana/contoh_sederhana.dart';

class Pengguna extends ManagedObject<_Pengguna> implements _Pengguna {}

class _Pengguna {
@primaryKey
int id;
@Column(unique: true)
String email;
@Column(unique: true)
String name;
}

Jadi, pada kode diatas kita akan membuat tabel pengguna dengan kolom id sebagai primary key lalu, ada kolom email dan name. Untuk melihat list annotation-nya bisa dilihat di link berikut ya.

Hubungkan ke Database

Sekarang kita akan menghubungkannya kedatabase dengan cara buka file config.yaml dan isikan dengan kode berikut.

database:
host: localhost
port: 5432
username: postgres
password: postgres
databaseName: tutorial_aqueduct

Adapun format dari kode diatas adalah seperti berikut.

database:
host: <host_database>
port: <port_database>
username: <username_database>
password: <password_database>
databaseName: <nama_database>

Database Migration

Sekarang kita akan membuat database migration-nya dengan cara menggunakan perintah berikut.

aqueduct db generate
Buat database migration

Sekarang pastikan apakah ada terbentuk file migration-nya.

File migration sudah terbentuk

Dan pastikan juga bahwa isi dari file migration-nya kurang lebih seperti berikut.

Isi file migration

Note: Jika file migration-nya tidak terbentuk atau kamu gagal dalam membuat file migration-nya maka, silakan kamu import saja class pengguna.dart didalam file channel.dart seperti gambar berikut. Lalu, jalankan ulang perintah aqueduct db generate

Jika diperlukan, import class pengguna.dart didalam file channel.dart

Hal tersebut berkaitan dengan issue ini.

Sekarang kita jalankan perintah berikut untuk membuat table pengguna.

pub run aqueduct db upgrade

Apakah kamu mendapatkan pesan error seperti berikut?

pub run aqueduct db upgrade gagal

Jika iya, maka kita sama-sama mengalaminya. Solusinya, coba kita buat file baru dengan nama database.yaml dan isikan dengan kode berikut.

username: postgres
password: postgres
host: localhost
port: 5432
databaseName: tutorial_aqueduct

Lalu, coba kita jalankan lagi perintah berikut.

pub run aqueduct db upgrade

Maka, hasilnya akan menjadi seperti berikut.

pub run aqueduct db upgrade berhasil

Mohon dipastikan saja bahwa PostgreSQL dilokalmu harus dalam keadaan hidup agar perintah-perintah diatas bisa berjalan dengan baik.

Sekarang kita cek apakah ada tabel yang terbuat didalam database tutorial_aqueduct.

Tabe pengguna sudah terbuat

Buat Endpoint CRUD Database

Untuk membuat endpoint yang bisa melakukan CRUD kedalam database silakan kita buat file baru dengan nama pengguna_controller.dart. Dan isikan dengan kode berikut.

import 'dart:convert';

import 'package:contoh_sederhana/contoh_sederhana.dart';
import 'package:contoh_sederhana/pengguna.dart';

class PenggunaController extends ResourceController {
final ManagedContext context;

PenggunaController(this.context);

@Operation.get()
Future<Response> getAllPengguna() async {
final listPengguna = await Query<Pengguna>(context).fetch();
return Response.ok(
{
'data': listPengguna.map((e) => e.asMap()).toList(),
},
);
}

@Operation.post()
Future<Response> createPengguna(@Bind.body() Pengguna inputPengguna) async {
final query = Query<Pengguna>(context)..values = inputPengguna;
final insertedPengguna = await query.insert();
return Response.ok(insertedPengguna);
}

@Operation.put('id')
Future<Response> updatePengguna(@Bind.path('id') int id, @Bind.body() Pengguna inputPengguna) async {
final query = Query<Pengguna>(context)
..where((element) => element.id).equalTo(id)
..values.name = inputPengguna.name
..values.email = inputPengguna.email;
final updatedPengguna = await query.updateOne();
if (updatedPengguna == null) {
return Response.notFound();
}
return Response.ok(updatedPengguna);
}

@Operation.delete('id')
Future<Response> deletePenggunaById(@Bind.path('id') int id) async {
final query = Query<Pengguna>(context)..where((element) => element.id).equalTo(id);
final deletedPengguna = await query.delete();
if (deletedPengguna > 0) {
return Response.ok({'message': 'Deleted successfully'});
}
return Response.notFound();
}
}

Kemudian, kita buka kembali file channel.dart dan ubah menjadi seperti berikut.

import 'package:contoh_sederhana/my_authorizer.dart';

import 'contoh_sederhana.dart';
import 'example_controller.dart';
import 'pengguna.dart';
import 'pengguna_controller.dart';


class ContohSederhanaChannel extends ApplicationChannel {
ManagedContext context;

@override
Future prepare() async {
logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));

final config = Config(options.configurationFilePath);
final dataModel = ManagedDataModel.fromCurrentMirrorSystem();
final persistentStore = PostgreSQLPersistentStore.fromConnectionInfo(
config.database.username,
config.database.password,
config.database.host,
config.database.port,
config.database.databaseName,
);
context = ManagedContext(dataModel, persistentStore);

}

@override
Controller get entryPoint {
final router = Router();
router.route('/example').link(() => MyAuthorizer()).link(() => ExampleController());
router.route('/pengguna/[:id]').link(() => MyAuthorizer()).link(() => PenggunaController(context));
return router;
}
}

class Config extends Configuration {
Config(String path) : super.fromFile(File(path));

DatabaseConfiguration database;
}

Sekarang mari kita test apakah endpoint CRUD pengguna sudah berjalan dengan baik atau tidak.

POST

Endpoint POST pengguna berhasil
Endpoint POST berhasil memasukkan data kedalam tabel pengguna

GET

Endpoint GET berhasil menampilkan data dari tabel pengguna

PUT

Endpoint PUT berhasil mengubah data
Endpoint PUT berhasil mengubah data di tabel pengguna

DELETE

Endpoint DELETE berhasil menghapus data
Endpoint DELETE berhasil menghapus data di tabel pengguna

Dan jangan lupa bahwa endpoint-endpoint CRUD tersebut kita harus menggunakan header berikut ya.

Header pada endpoint CRUD

Kesimpulan

Jadi, setelah saya bahas mulai dari contoh sederhana sampai menghubungkan kedatabase saya rasa kesimpulannya adalah bahwa Aqueduct ini termasuk Dart Server Side Framework yang masih tergolong gampang untuk penggunaannya. Yang terpenting ialah buat kita semua harus rajin-rajin saja membaca dokumentasinya dan issue di Github-nya karena, jujur saja pada saat penulisan tutorial ini saya lumayan banyak mengalami try and error-nya. Tapi, walaupun banyak try and error-nya bukan berarti, Aqueduct belum stabil atau tidak siap production ya. Ini hanya masalah belum terbiasa saja untuk menggunakannya. Untuk kode lengkapnya pada contoh projek ditulisan ini bisa kamu lihat di Github ya.

--

--