M
Mr Sugiarto
Developer
18 Aug 2026 4 min read

Setelah di part sebelumnya kita berhasil membuat endpoint GET untuk list dan detail post, di part ini kita akan lanjut membuat endpoint untuk Create (Store), Update, dan Delete data post — lengkap dengan validasi input sebelum data disimpan ke database.

Menambahkan Method Store, Update, dan Destroy di PostController

Buka file app/http/controllers/post_controller.go, kemudian tambahkan method Store:

func (r *PostController) Store(ctx http.Context) http.Response {
	validator, err := ctx.Request().Validate(map[string]string{
		"title": "required|max_len:255",
		"body":  "required",
	})

	if err != nil {
		return ctx.Response().Json(http.StatusInternalServerError, http.Json{
			"error": err.Error(),
		})
	}

	if validator.Fails() {
		return ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
			"errors": validator.Errors().All(),
		})
	}

	post := models.Post{
		Title:  ctx.Request().Input("title"),
		Body:   ctx.Request().Input("body"),
		Status: "DRAFT",
	}

	if err := facades.Orm().Query().Create(&post); err != nil {
		return ctx.Response().Json(http.StatusBadRequest, http.Json{
			"error": err.Error(),
		})
	}

	return ctx.Response().Json(http.StatusCreated, post)
}

Perhatikan di method Store ini kita menggunakan ctx.Request().Validate() untuk memvalidasi input sebelum data disimpan. Rule required memastikan field wajib diisi, sedangkan max_len:255 membatasi panjang karakter title. Kalau validasi gagal, endpoint akan merespon dengan status 422 Unprocessable Entity beserta detail error-nya. Post baru yang dibuat otomatis diberi status DRAFT.

Selanjutnya method Update:

func (r *PostController) Update(ctx http.Context) http.Response {
	id := ctx.Request().Route("id")

	var post models.Post
	if err := facades.Orm().Query().Where("id", id).First(&post); err != nil {
		return ctx.Response().Json(http.StatusNotFound, http.Json{
			"error": "Post not found",
		})
	}

	validator, err := ctx.Request().Validate(map[string]string{
		"title": "required|max_len:255",
		"body":  "required",
	})

	if err != nil {
		return ctx.Response().Json(http.StatusInternalServerError, http.Json{
			"error": err.Error(),
		})
	}

	if validator.Fails() {
		return ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
			"errors": validator.Errors().All(),
		})
	}

	post.Title = ctx.Request().Input("title")
	post.Body = ctx.Request().Input("body")
	if ctx.Request().Input("status") != "" {
		post.Status = ctx.Request().Input("status")
	}

	if err := facades.Orm().Query().Save(&post); err != nil {
		return ctx.Response().Json(http.StatusBadRequest, http.Json{
			"error": err.Error(),
		})
	}

	return ctx.Response().Json(http.StatusOK, post)
}

Di method ini kita cari dulu post berdasarkan id dari parameter route. Kalau tidak ketemu, langsung return 404 Not Found. Field status sifatnya opsional — kalau dikirim, statusnya akan diupdate (misalnya dari DRAFT ke ACTIVE).

Terakhir method Destroy:

func (r *PostController) Destroy(ctx http.Context) http.Response {
	id := ctx.Request().Route("id")

	var post models.Post
	if err := facades.Orm().Query().Where("id", id).First(&post); err != nil {
		return ctx.Response().Json(http.StatusNotFound, http.Json{
			"error": "Post not found",
		})
	}

	if _, err := facades.Orm().Query().Delete(&post); err != nil {
		return ctx.Response().Json(http.StatusBadRequest, http.Json{
			"error": err.Error(),
		})
	}

	return ctx.Response().Json(http.StatusOK, http.Json{"success": true})
}

Karena model Post menggunakan orm.Model yang sudah include SoftDeletes (lihat kembali migration di Part 4), method Delete di sini sebenarnya melakukan soft delete — data tidak benar-benar hilang dari database, hanya ditandai terhapus lewat kolom deleted_at.

Menambahkan Route

Daftarkan ketiga endpoint baru ini di routes/api.go:

package routes

import (
	"github.com/goravel/framework/facades"

	"myblog/app/http/controllers"
)

func Api() {
	userController := controllers.NewUserController()
	facades.Route().Get("/users/{id}", userController.Show)

	postContoller := controllers.NewPostController()
	facades.Route().Get("/posts", postContoller.Index)
	facades.Route().Get("/posts/{id}", postContoller.Show)
	facades.Route().Post("/posts", postContoller.Store)
	facades.Route().Put("/posts/{id}", postContoller.Update)
	facades.Route().Delete("/posts/{id}", postContoller.Destroy)
}

Sampai di part ini, endpoint create/update/delete masih bisa diakses oleh siapa saja tanpa autentikasi. Ini akan kita perbaiki nanti setelah kita bahas Register, Login, dan JWT Token di part berikutnya.

Testing Endpoint

Jalankan ulang server (go run .), lalu coba endpoint berikut menggunakan Postman atau REST client lainnya:

  • POST /posts dengan body {"title": "...", "body": "..."} → membuat post baru
  • PUT /posts/{id} dengan body yang sama → update post
  • DELETE /posts/{id} → hapus post (soft delete)

Coba juga kirim request tanpa title atau body untuk melihat response validasi error-nya.

Video Tutorial

Tutorial dalam bentuk video dapat di akses di channel Youtube IndoKoding: https://www.youtube.com/watch?v=YoCsr7fGuiE

M
Mr Sugiarto

Developer

Bagian dari Series: Seri Tutorial Belajar Framework Goravel Rest API untuk Pemula

Overview Seri ini membahas cara membangun REST API menggunakan Goravel, framework web berbasis Golang yang terinspirasi dari Laravel. Materi mencakup...

Lihat Series Lengkap