Authentication
Register and log in. Passwords are hashed with bcrypt, and login returns a JWT that expires after 24 hours.
PROJECT 02 · BACKEND REST API
Task API is a REST API written in Go with Gin and GORM. Users register, log in with a JWT, and manage their own tasks. It runs on SQLite with no database setup and comes with Swagger documentation and a Postman collection.
01 POST /api/auth/register
02 POST /api/auth/login
03 GET /api/me
04 GET /api/tasks
05 POST /api/tasks
{
"data": [],
"meta": { "page": 1, "limit": 10, "total": 0 }
}01 / SCOPE
Every user has a private task list. The project is about the parts around plain CRUD: authentication, access limited to the owner, input validation, pagination, and documentation that another developer can run.
02 / WHAT IS INCLUDED
Register and log in. Passwords are hashed with bcrypt, and login returns a JWT that expires after 24 hours.
Create, read, update, and delete your own tasks, with paginated lists and a done filter.
Routes in main.go, with handlers, JWT middleware, models, and token utilities in separate packages.
An OpenAPI spec with Swagger UI at /docs, a Postman collection, and a multi-stage Dockerfile.
API ENDPOINTS
| METHOD | PATH | ACCESS | NOTE |
|---|---|---|---|
| GET | /health | Public | Server status check. |
| POST | /api/auth/register | Public | Create an account. The password is hashed with bcrypt; a duplicate email returns 409. |
| POST | /api/auth/login | Public | Returns a JWT that is valid for 24 hours. |
| GET | /api/me | Login required | Profile of the logged-in user. |
| GET | /api/tasks | Login required | Your tasks, newest first. Query: page, limit, done. |
| POST | /api/tasks | Login required | Create a task. The title is required. |
| GET | /api/tasks/:id | Login required | One of your tasks. A task of another user returns 404. |
| PUT | /api/tasks/:id | Login required | Partial update: send only the fields to change. |
| DELETE | /api/tasks/:id | Login required | Delete one of your tasks. |
RECORDED REQUESTS
15 requests recorded from the API running on my laptop on October 20, 2026. Tokens are shortened.
/health200Check that the server is upGET /healthHTTP 200 OK { "status": "ok" }
/api/auth/register201Create an accountPOST /api/auth/register Content-Type: application/json { "name": "Rafi", "email": "rafi.demo@example.com", "password": "rahasia123" }
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" }
/api/auth/register409Same email again is rejectedPOST /api/auth/register Content-Type: application/json { "name": "Rafi", "email": "rafi.demo@example.com", "password": "rahasia123" }
HTTP 409 Conflict { "error": "email sudah terdaftar" }
/api/auth/register400Invalid input is rejectedPOST /api/auth/register Content-Type: application/json { "name": "Rafi", "email": "not-an-email", "password": "123" }
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" }
/api/auth/login200Log in and receive a JWTPOST /api/auth/login Content-Type: application/json { "email": "rafi.demo@example.com", "password": "rahasia123" }
HTTP 200 OK { "expires_in": 86400, "token": "eyJhbGciOiJIUzI1Ni…", "token_type": "Bearer" }
/api/auth/login401A wrong password gives 401POST /api/auth/login Content-Type: application/json { "email": "rafi.demo@example.com", "password": "salah-total" }
HTTP 401 Unauthorized { "error": "email atau password salah" }
/api/me200Profile of the logged-in userGET /api/me Authorization: Bearer eyJhbGciOiJIUzI1Ni…
HTTP 200 OK { "data": { "id": 1, "name": "Rafi", "email": "rafi.demo@example.com", "created_at": "2026-10-20T15:38:56.913128552+07:00" } }
/api/tasks401No token, no accessGET /api/tasksHTTP 401 Unauthorized { "error": "token tidak ditemukan" }
/api/tasks201Create a taskPOST /api/tasks Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1Ni… { "title": "Belajar goroutine", "description": "Baca dokumentasi dan buat contoh" }
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" } }
/api/tasks201Create a second taskPOST /api/tasks Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1Ni… { "title": "Menulis tes untuk handler" }
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" } }
/api/tasks?page=1&limit=10200List tasks with paginationGET /api/tasks?page=1&limit=10 Authorization: Bearer eyJhbGciOiJIUzI1Ni…
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 } }
/api/tasks/1200Update only one fieldPUT /api/tasks/1 Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1Ni… { "done": true }
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" } }
/api/tasks?done=true200Filter by doneGET /api/tasks?done=true Authorization: Bearer eyJhbGciOiJIUzI1Ni…
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 } }
/api/tasks/9999404A task that does not existGET /api/tasks/9999 Authorization: Bearer eyJhbGciOiJIUzI1Ni…
HTTP 404 Not Found { "error": "task tidak ditemukan" }
/api/tasks/2200Delete a taskDELETE /api/tasks/2 Authorization: Bearer eyJhbGciOiJIUzI1Ni…
HTTP 200 OK { "message": "task dihapus" }
DESIGN DECISIONS
A task is fetched with both its id and the user id from the token, so a user cannot read, change, or delete a task that belongs to someone else. Such a task returns 404, the same as a task that does not exist.
The update endpoint reads title, description, and done as pointers. That separates a field that was not sent from an empty value or false, so done: false is applied while an omitted field is left alone.
Passwords are hashed with bcrypt and never appear in a response. Login gives the same message for a wrong email and a wrong password. Tokens are HS256 JWTs that expire after 24 hours, and the parser accepts only that signing method.
The list endpoint defaults to page 1 with 10 items, accepts a limit from 1 to 100 and falls back to 10 for anything else, and returns a meta object with page, limit, and total so a client can build its own pager.
GORM uses a pure-Go SQLite driver, so the API starts with go run and nothing else to install. The database is one file, configured with DB_PATH.
03 / NEXT IMPROVEMENTS
The API has no automated tests yet, and the JWT secret falls back to a default value when the environment variable is not set, so a production setup should refuse to start without it. Next I would add handler tests, rate limiting on login, and a PostgreSQL option.