R.RAFI BAKHTIAR
SOFTWARE ENGINEER
TERBUKA UNTUK KERJA CV ↗
← KEMBALI KE PROYEK

PROYEK 02 · REST API BACKEND

Task
API.

task-api / routesGO · GIN · GORM

01 POST /api/auth/register

02 POST /api/auth/login

03 GET /api/me

04 GET /api/tasks

05 POST /api/tasks

RESPONSE · JSON (EMPTY LIST)
{
  "data": [],
  "meta": { "page": 1, "limit": 10, "total": 0 }
}
JENIS PROYEKBackend REST API
PERANBackend developer
STACKGo · Gin · GORM · SQLite

01 / RUANG LINGKUP

API kecil, dibangun dengan struktur.

Setiap pengguna punya daftar task pribadi. Fokus proyek ini ada pada bagian di luar CRUD biasa: autentikasi, akses yang dibatasi untuk pemilik, validasi input, pagination, dan dokumentasi yang bisa dijalankan developer lain.

02 / ISI PROYEK

Autentikasi, task,
dan dokumentasi.

01

Autentikasi

Registrasi dan login. Password di-hash dengan bcrypt, dan login mengembalikan JWT yang kedaluwarsa setelah 24 jam.

02

Manajemen task

Membuat, membaca, mengubah, dan menghapus task sendiri, dengan daftar per halaman dan filter selesai.

03

Kode berlapis

Route di main.go, dengan handler, middleware JWT, model, dan utilitas token di package terpisah.

04

Dokumentasi

Spesifikasi OpenAPI dengan Swagger UI di /docs, koleksi Postman, dan Dockerfile multi-stage.

ENDPOINT API

API sekilas.

METHODPATHAKSESCATATAN
GET /health Publik Memeriksa status server.
POST /api/auth/register Publik Membuat akun. Password di-hash dengan bcrypt; email yang sudah terdaftar menghasilkan 409.
POST /api/auth/login Publik Mengembalikan JWT yang berlaku 24 jam.
GET /api/me Perlu login Profil pengguna yang sedang login.
GET /api/tasks Perlu login Task milikmu, terbaru lebih dulu. Query: page, limit, done.
POST /api/tasks Perlu login Membuat task. Judul wajib diisi.
GET /api/tasks/:id Perlu login Satu task milikmu. Task milik pengguna lain menghasilkan 404.
PUT /api/tasks/:id Perlu login Update sebagian: kirim hanya field yang ingin diubah.
DELETE /api/tasks/:id Perlu login Menghapus salah satu task milikmu.

REQUEST TEREKAM

Request nyata, respons nyata.

15 request direkam dari API yang berjalan di laptop saya pada 20 Oktober 2026. Token dipersingkat.

01GET/health200Memeriksa server berjalan

REQUEST

GET /health

RESPONS

HTTP 200 OK

{
  "status": "ok"
}
02POST/api/auth/register201Membuat akun

REQUEST

POST /api/auth/register
Content-Type: application/json

{
  "name": "Rafi",
  "email": "rafi.demo@example.com",
  "password": "rahasia123"
}

RESPONS

HTTP 201 Created

{
  "data": {
    "id": 1,
    "name": "Rafi",
    "email": "rafi.demo@example.com",
    "created_at": "2026-10-20T15:38:56.913128552+07:00"
  },
  "message": "registrasi berhasil"
}
03POST/api/auth/register409Email yang sama ditolak

REQUEST

POST /api/auth/register
Content-Type: application/json

{
  "name": "Rafi",
  "email": "rafi.demo@example.com",
  "password": "rahasia123"
}

RESPONS

HTTP 409 Conflict

{
  "error": "email sudah terdaftar"
}
04POST/api/auth/register400Input tidak valid ditolak

REQUEST

POST /api/auth/register
Content-Type: application/json

{
  "name": "Rafi",
  "email": "not-an-email",
  "password": "123"
}

RESPONS

HTTP 400 Bad Request

{
  "error": "Key: 'registerInput.Email' Error:Field validation for 'Email' failed on the 'email' tag\nKey: 'registerInput.Password' Error:Field validation for 'Password' failed on the 'min' tag"
}
05POST/api/auth/login200Login dan menerima JWT

REQUEST

POST /api/auth/login
Content-Type: application/json

{
  "email": "rafi.demo@example.com",
  "password": "rahasia123"
}

RESPONS

HTTP 200 OK

{
  "expires_in": 86400,
  "token": "eyJhbGciOiJIUzI1Ni…",
  "token_type": "Bearer"
}
06POST/api/auth/login401Password salah menghasilkan 401

REQUEST

POST /api/auth/login
Content-Type: application/json

{
  "email": "rafi.demo@example.com",
  "password": "salah-total"
}

RESPONS

HTTP 401 Unauthorized

{
  "error": "email atau password salah"
}
07GET/api/me200Profil pengguna yang login

REQUEST

GET /api/me
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

RESPONS

HTTP 200 OK

{
  "data": {
    "id": 1,
    "name": "Rafi",
    "email": "rafi.demo@example.com",
    "created_at": "2026-10-20T15:38:56.913128552+07:00"
  }
}
08GET/api/tasks401Tanpa token, tidak ada akses

REQUEST

GET /api/tasks

RESPONS

HTTP 401 Unauthorized

{
  "error": "token tidak ditemukan"
}
09POST/api/tasks201Membuat task

REQUEST

POST /api/tasks
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

{
  "title": "Belajar goroutine",
  "description": "Baca dokumentasi dan buat contoh"
}

RESPONS

HTTP 201 Created

{
  "data": {
    "id": 1,
    "user_id": 1,
    "title": "Belajar goroutine",
    "description": "Baca dokumentasi dan buat contoh",
    "done": false,
    "created_at": "2026-10-20T15:38:57.067602292+07:00",
    "updated_at": "2026-10-20T15:38:57.067602292+07:00"
  }
}
10POST/api/tasks201Membuat task kedua

REQUEST

POST /api/tasks
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

{
  "title": "Menulis tes untuk handler"
}

RESPONS

HTTP 201 Created

{
  "data": {
    "id": 2,
    "user_id": 1,
    "title": "Menulis tes untuk handler",
    "description": "",
    "done": false,
    "created_at": "2026-10-20T15:38:57.112172832+07:00",
    "updated_at": "2026-10-20T15:38:57.112172832+07:00"
  }
}
11GET/api/tasks?page=1&limit=10200Daftar task dengan pagination

REQUEST

GET /api/tasks?page=1&limit=10
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

RESPONS

HTTP 200 OK

{
  "data": [
    {
      "id": 2,
      "user_id": 1,
      "title": "Menulis tes untuk handler",
      "description": "",
      "done": false,
      "created_at": "2026-10-20T15:38:57.112172832+07:00",
      "updated_at": "2026-10-20T15:38:57.112172832+07:00"
    },
    {
      "id": 1,
      "user_id": 1,
      "title": "Belajar goroutine",
      "description": "Baca dokumentasi dan buat contoh",
      "done": false,
      "created_at": "2026-10-20T15:38:57.067602292+07:00",
      "updated_at": "2026-10-20T15:38:57.067602292+07:00"
    }
  ],
  "meta": {
    "limit": 10,
    "page": 1,
    "total": 2
  }
}
12PUT/api/tasks/1200Mengubah satu field saja

REQUEST

PUT /api/tasks/1
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

{
  "done": true
}

RESPONS

HTTP 200 OK

{
  "data": {
    "id": 1,
    "user_id": 1,
    "title": "Belajar goroutine",
    "description": "Baca dokumentasi dan buat contoh",
    "done": true,
    "created_at": "2026-10-20T15:38:57.067602292+07:00",
    "updated_at": "2026-10-20T15:38:57.133681456+07:00"
  }
}
13GET/api/tasks?done=true200Filter berdasarkan done

REQUEST

GET /api/tasks?done=true
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

RESPONS

HTTP 200 OK

{
  "data": [
    {
      "id": 1,
      "user_id": 1,
      "title": "Belajar goroutine",
      "description": "Baca dokumentasi dan buat contoh",
      "done": true,
      "created_at": "2026-10-20T15:38:57.067602292+07:00",
      "updated_at": "2026-10-20T15:38:57.133681456+07:00"
    }
  ],
  "meta": {
    "limit": 10,
    "page": 1,
    "total": 1
  }
}
14GET/api/tasks/9999404Task yang tidak ada

REQUEST

GET /api/tasks/9999
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

RESPONS

HTTP 404 Not Found

{
  "error": "task tidak ditemukan"
}
15DELETE/api/tasks/2200Menghapus task

REQUEST

DELETE /api/tasks/2
Authorization: Bearer eyJhbGciOiJIUzI1Ni…

RESPONS

HTTP 200 OK

{
  "message": "task dihapus"
}

KEPUTUSAN DESAIN

Pilihan yang saya ambil, beserta alasannya.

01

Setiap query task dibatasi untuk pengguna yang login

Task diambil dengan id task dan id pengguna dari token sekaligus, sehingga pengguna tidak bisa membaca, mengubah, atau menghapus task milik orang lain. Task seperti itu menghasilkan 404, sama seperti task yang memang tidak ada.

02

Update sebagian dengan field pointer

Endpoint update membaca title, description, dan done sebagai pointer. Dengan begitu field yang tidak dikirim dibedakan dari nilai kosong atau false: done: false diterapkan, sedangkan field yang tidak dikirim dibiarkan.

03

Password dan token

Password di-hash dengan bcrypt dan tidak pernah muncul di respons. Login memberi pesan yang sama untuk email salah maupun password salah. Token berupa JWT HS256 yang kedaluwarsa setelah 24 jam, dan parser hanya menerima metode tanda tangan itu.

04

Pagination dengan batas yang aman

Endpoint daftar memakai halaman 1 dengan 10 item sebagai bawaan, menerima limit 1 sampai 100 dan kembali ke 10 untuk nilai lain, serta mengirim objek meta berisi page, limit, dan total agar klien bisa membuat pager sendiri.

05

Berjalan tanpa server database

GORM memakai driver SQLite murni Go, sehingga API langsung jalan dengan go run tanpa perlu memasang apa pun. Database-nya satu file yang diatur lewat DB_PATH.

03 / LANGKAH BERIKUTNYA

Yang masih kurang.

API ini belum punya tes otomatis, dan secret JWT memakai nilai bawaan bila variabel lingkungan tidak diisi, jadi di production aplikasi seharusnya menolak berjalan tanpa secret. Langkah berikutnya: tes untuk handler, pembatasan laju di login, dan opsi PostgreSQL.

DISKUSIKAN PROYEK INI ↗ PROYEK BERIKUTNYA: D'ROLLS ↗