handler.go — JSON API Handlers

REST handlers for the /api/bookmarks endpoints. Uses writeJSON and writeError helpers for consistent responses.

package main

import (
	"encoding/json"
	"errors"
	"log/slog"
	"net/http"
	"strconv"
)

func writeJSON(w http.ResponseWriter, status int, data any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, message string) {
	writeJSON(w, status, map[string]string{"error": message})
}

func handleListBookmarks(store *BookmarkStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		bookmarks, err := store.List(r.Context())
		if err != nil {
			slog.Error("list bookmarks", "err", err)
			writeError(w, http.StatusInternalServerError, "internal error")
			return
		}
		if bookmarks == nil {
			bookmarks = []Bookmark{}
		}
		writeJSON(w, http.StatusOK, bookmarks)
	}
}

func handleCreateBookmark(store *BookmarkStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var input struct {
			URL   string `json:"url"`
			Title string `json:"title"`
		}
		if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
			writeError(w, http.StatusBadRequest, "invalid JSON")
			return
		}
		if input.URL == "" {
			writeError(w, http.StatusBadRequest, "url is required")
			return
		}
		if input.Title == "" {
			writeError(w, http.StatusBadRequest, "title is required")
			return
		}
		bookmark, err := store.Create(r.Context(), input.URL, input.Title)
		if err != nil {
			slog.Error("create bookmark", "err", err)
			writeError(w, http.StatusInternalServerError, "internal error")
			return
		}
		slog.Info("bookmark created", "id", bookmark.ID, "url", bookmark.URL)
		writeJSON(w, http.StatusCreated, bookmark)
	}
}

func handleGetBookmark(store *BookmarkStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id, err := strconv.Atoi(r.PathValue("id"))
		if err != nil {
			writeError(w, http.StatusBadRequest, "invalid id")
			return
		}
		bookmark, err := store.GetByID(r.Context(), id)
		if errors.Is(err, ErrNotFound) {
			writeError(w, http.StatusNotFound, "bookmark not found")
			return
		}
		if err != nil {
			slog.Error("get bookmark", "err", err, "id", id)
			writeError(w, http.StatusInternalServerError, "internal error")
			return
		}
		writeJSON(w, http.StatusOK, bookmark)
	}
}

func handleDeleteBookmark(store *BookmarkStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id, err := strconv.Atoi(r.PathValue("id"))
		if err != nil {
			writeError(w, http.StatusBadRequest, "invalid id")
			return
		}
		err = store.Delete(r.Context(), id)
		if errors.Is(err, ErrNotFound) {
			writeError(w, http.StatusNotFound, "bookmark not found")
			return
		}
		if err != nil {
			slog.Error("delete bookmark", "err", err, "id", id)
			writeError(w, http.StatusInternalServerError, "internal error")
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

💻 Run locally

Copy the code above and run it on your machine

© 2026 ByteLearn.dev. Free courses for developers. · Privacy