Bookmark Store

CRUD operations with database/sql and lib/pq.

package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"log"
	"time"

	_ "github.com/lib/pq"
)

var ErrNotFound = errors.New("bookmark not found")

type Bookmark struct {
	ID        int       `json:"id"`
	URL       string    `json:"url"`
	Title     string    `json:"title"`
	CreatedAt time.Time `json:"created_at"`
}

type BookmarkStore struct {
	db *sql.DB
}

func NewBookmarkStore(db *sql.DB) *BookmarkStore {
	return &BookmarkStore{db: db}
}

func (s *BookmarkStore) Create(ctx context.Context, url, title string) (Bookmark, error) {
	var b Bookmark
	err := s.db.QueryRowContext(ctx,
		"INSERT INTO bookmarks (url, title) VALUES ($1, $2) RETURNING id, url, title, created_at",
		url, title,
	).Scan(&b.ID, &b.URL, &b.Title, &b.CreatedAt)
	if err != nil {
		return Bookmark{}, fmt.Errorf("create bookmark: %w", err)
	}
	return b, nil
}

func (s *BookmarkStore) GetByID(ctx context.Context, id int) (Bookmark, error) {
	var b Bookmark
	err := s.db.QueryRowContext(ctx,
		"SELECT id, url, title, created_at FROM bookmarks WHERE id = $1", id,
	).Scan(&b.ID, &b.URL, &b.Title, &b.CreatedAt)
	if errors.Is(err, sql.ErrNoRows) {
		return Bookmark{}, ErrNotFound
	}
	if err != nil {
		return Bookmark{}, fmt.Errorf("get bookmark: %w", err)
	}
	return b, nil
}

func (s *BookmarkStore) List(ctx context.Context) ([]Bookmark, error) {
	rows, err := s.db.QueryContext(ctx,
		"SELECT id, url, title, created_at FROM bookmarks ORDER BY created_at DESC",
	)
	if err != nil {
		return nil, fmt.Errorf("list bookmarks: %w", err)
	}
	defer rows.Close()

	var bookmarks []Bookmark
	for rows.Next() {
		var b Bookmark
		if err := rows.Scan(&b.ID, &b.URL, &b.Title, &b.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan bookmark: %w", err)
		}
		bookmarks = append(bookmarks, b)
	}
	return bookmarks, rows.Err()
}

func (s *BookmarkStore) Delete(ctx context.Context, id int) error {
	result, err := s.db.ExecContext(ctx, "DELETE FROM bookmarks WHERE id = $1", id)
	if err != nil {
		return fmt.Errorf("delete bookmark: %w", err)
	}
	rows, _ := result.RowsAffected()
	if rows == 0 {
		return ErrNotFound
	}
	return nil
}

func main() {
	db, err := sql.Open("postgres", "postgres://bookmarks:localdev@localhost:5432/bookmarks?sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	store := NewBookmarkStore(db)
	ctx := context.Background()

	b, err := store.Create(ctx, "https://go.dev", "Go Homepage")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Created: %d%s\n", b.ID, b.Title)

	bookmarks, err := store.List(ctx)
	if err != nil {
		log.Fatal(err)
	}
	for _, bm := range bookmarks {
		fmt.Printf("  %d: %s (%s)\n", bm.ID, bm.Title, bm.URL)
	}
}

💻 Run locally

Copy the code above and run it on your machine

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