All work

Go

golang-blog-api

Stars0
LanguageGo
Last push6 Sept 2026

Blog API

A small blogging REST API written in Go, using Gin, SQLite, and JWT-based authentication.

Features

  • User registration & login with bcrypt-hashed passwords
  • JWT authentication (7-day expiry) protecting all blog routes
  • Blog CRUD, scoped to the authenticated user (you can only update/delete your own blogs)
  • Pagination on blog listing endpoints
  • Config via environment variables / .env (see below)
  • Graceful shutdown on SIGINT/SIGTERM

Requirements

  • Go 1.26+
  • SQLite (bundled via mattn/go-sqlite3, no separate install needed)

Setup

git clone git@github.com:Kyaw-Min-Khant/golang-blog-api.git
cd golang-blog-api
cp .env.example .env

Edit .env and set a real JWT_SECRET — generate one with:

openssl rand -base64 32

Run it:

go run .

Or with live-reload during development (air):

air

Run the tests:

go test ./...

Configuration

All config is read from environment variables (.env is loaded automatically if present; it's a no-op if it's missing, which is the case in most production deployments where the platform injects env vars directly).

VariableDefaultNotes
APP_ENVdevelopmentSet to production to enable release mode. Also enforces that JWT_SECRET must be set (the server refuses to start without it).
PORT8080HTTP port to listen on.
DB_PATHblog.dbPath to the SQLite database file. Use a different value per environment — see Deployment.
JWT_SECRET(insecure dev default)Secret used to sign JWTs. Must be set explicitly outside local dev.

API Reference

Base path: v1/api

All routes except health-check, auth/register, and auth/login require an Authorization: Bearer <token> header.

MethodPathAuthDescription
GET/health-checkNoLiveness check
POST/auth/registerNoCreate a user account
POST/auth/loginNoLog in, returns a JWT
GET/profileYesCurrent authenticated user's identity
POST/blogYesCreate a blog post
GET/blogYesList all blogs (paginated)
GET/blog/userYesList the current user's own blogs (paginated)
PATCH/blog/:blog_idYesUpdate a blog you own
DELETE/blog/:blog_idYesDelete a blog you own

Pagination query params (on GET /blog and GET /blog/user): limit (default 10, max 100) and page (offset, default 0).

Response shape

Every response follows the same envelope:

{ "status": "success", "data": { ... } }
{ "status": "error", "message": "..." }

Example: register, login, create a blog

curl -X POST http://localhost:8080/v1/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"secretpw"}'

TOKEN=$(curl -s -X POST http://localhost:8080/v1/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"secretpw"}' | jq -r .data.token)

curl -X POST http://localhost:8080/v1/api/blog \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Hello","content":"World"}'

Project Structure

FileResponsibility
main.goWiring: config, routes, HTTP server, graceful shutdown
config.goEnv var / .env loading
db.goSQLite connection + schema migration
models.goRequest/response structs
store.goDatabase queries
handlers.goHTTP handlers
auth.goPassword hashing, JWT generation/parsing
middleware.goJWT auth middleware
response.goUniform success/error JSON helpers

Deployment

Deployed on Render as a native Go web service:

  • Build Command: go build -o app .
  • Start Command: ./app
  • Root Directory: leave blank (this repo is the service root)
  • Environment: set APP_ENV=production and JWT_SECRET under the service's Environment tab (PORT is provided automatically by Render)
  • Persistent storage: SQLite needs a real file on disk that survives redeploys. Attach a Render Disk and point DB_PATH at a path inside it — without this, the database resets on every deploy.

Known limitations

  • SQLite is single-writer; if this ever needs to run as more than one instance/process, move to a real DB server (Postgres/MySQL) instead of scaling SQLite.

Also in Go