Todo API is a RESTful task-management backend written in Go. It provides user registration and login, JWT-based authentication, and user-scoped CRUD operations for todo items.
The API is designed as a small, maintainable backend service with PostgreSQL persistence. It can be run locally with Go or through Docker Compose, with a development container configured for hot reloading.
Todo API is intended for:
- Developers building a todo or productivity client that needs a simple authenticated backend.
- Learners exploring REST API design, JWT authentication, PostgreSQL, and Go project structure.
- Small applications that need isolated task lists for multiple users.
- π User authentication - Register accounts with bcrypt-hashed passwords and sign in to receive a JWT.
- β Authenticated task management - Create, read, update, and delete tasks belonging to the signed-in user.
- ποΈ PostgreSQL persistence - Store users and tasks in PostgreSQL with versioned SQL migrations.
- π§± Layered project structure - Separate configuration, database access, handlers, middleware, models, and repositories.
- π³ Docker support - Run the API and PostgreSQL together with Docker Compose.
- π Development hot reload - Use Air in the development image to rebuild the API when source files change.
- π§© Language: Go 1.26.5
- π HTTP framework: Gin v1.12.0
- πΎ Database: PostgreSQL 16
- π Database driver: pgx v5
- π Authentication: JWT with HS256
- π Password hashing: bcrypt
- π³ Containerization: Docker and Docker Compose
- π οΈ Database migrations: golang-migrate
- π¦ Configuration:
.envfiles and environment variables viagodotenv
The default base URL is http://localhost:8000.
| Method | Endpoint | Authentication | Description |
|---|---|---|---|
GET |
/ |
No | Health response confirming the service and database are available. |
POST |
/auth/signup |
No | Create a user account. |
POST |
/auth/login |
No | Authenticate and receive a JWT. |
POST |
/task |
Bearer token | Create a task. |
GET |
/task/all |
Bearer token | List the authenticated user's tasks. |
GET |
/task/:id |
Bearer token | Get one of the authenticated user's tasks. |
PUT |
/task/:id |
Bearer token | Update one of the authenticated user's tasks. |
DELETE |
/task/:id |
Bearer token | Delete one of the authenticated user's tasks. |
Protected endpoints require the header:
Authorization: Bearer <jwt>Create an account:
curl -X POST http://localhost:8000/auth/signup \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123","confirm_password":"password123"}'Log in:
curl -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'Create a task using the token returned by login:
curl -X POST http://localhost:8000/task \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"title":"Read the API documentation","description":"Review the available endpoints","is_completed":false}'Todo API was built around a focused backend workflow:
- π€ Keep each user's tasks isolated through authenticated, user-scoped queries.
- π Protect account credentials with bcrypt and protect API access with expiring JWTs.
- π§± Keep responsibilities separated so handlers, repositories, and middleware can evolve independently.
- π οΈ Make local development repeatable with migrations and containerized PostgreSQL.
Choose one of the following setup options:
- Go 1.26.5 or later, PostgreSQL 16, and the
migrateCLI for local development. - Docker and Docker Compose for the containerized setup.
Create a .env file in the project root:
DB_USER=superuser
DB_PASSWORD=your_password
DB_NAME=todo_app_db
DB_HOST=localhost
DB_PORT=5432
DATABASE_URL=postgres://superuser:your_password@localhost:5432/todo_app_db?sslmode=disable
APP_PORT=8000
SECRET_KEY=replace_with_a_long_random_secretKeep .env out of version control and use a strong, unique SECRET_KEY outside local development.
Install Docker Desktop or Docker Engine with the Docker Compose plugin. Verify that both commands are available:
docker --version
docker compose version- Clone the repository and enter the project directory:
git clone <repository-url>
cd to-do-app- Create a
.envfile in the project root. Usedbas the database host because the API connects to PostgreSQL through the Compose network:
DB_USER=superuser
DB_PASSWORD=your_password
DB_NAME=todo_app_db
DB_HOST=db
DB_PORT=5432
DATABASE_URL=postgres://superuser:your_password@db:5432/todo_app_db?sslmode=disable
APP_PORT=8000
SECRET_KEY=replace_with_a_long_random_secret- Start the app and database containers:
docker compose up --build- In a separate terminal, apply the database migrations:
./scripts/migrate.sh- Open the API at
http://localhost:8000. The development image uses Air for hot reloading.
Stop the services and remove the containers with:
docker compose downThe PostgreSQL data is stored in the postgres_data named volume and remains available after stopping the services. To remove the volume as well, use:
docker compose down -vBuild the production image defined in Dockerfile:
docker build -t todo-api .Run the image with environment variables and publish the API port:
docker run --env-file .env -p 8000:8000 todo-apiThe production container still requires access to a running PostgreSQL instance. Set DATABASE_URL to a hostname reachable from the container before starting it.
This project uses the Go migrate CLI and the wrapper script in scripts/migrate.sh. The script automatically starts the PostgreSQL container, waits for it to become healthy, and then forwards every argument to the migrate command.
./scripts/migrate.sh upThis is the usual setup command after cloning the repo or when you want to bring the database to the latest version.
./scripts/migrate.sh down 1This reverts the latest migration batch. Change the number to roll back multiple steps, for example: ./scripts/migrate.sh down 2.
./scripts/migrate.sh downThis will revert all applied migrations in reverse order.
Use this when a migration is marked as dirty or a previous migration needs to be forced in the schema_migrations table:
./scripts/migrate.sh force <migration-number>Replace the value with the desired migration version number. This is mainly for recovery and should be used carefully.
./scripts/migrate.sh versionThis prints the current migration version tracked by the database.
Tip
If you want to run the API without Docker, install the required tools locally and point the app to a local PostgreSQL instance.
Download and install Go from the official site:
After installation, confirm it is available:
go versionInstall PostgreSQL locally and create a database for the app:
Example local database values:
DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=todo_app_db
DB_HOST=localhost
DB_PORT=5432
DATABASE_URL=postgres://postgres:your_password@localhost:5432/todo_app_db?sslmode=disable
APP_PORT=8000
SECRET_KEY=replace_with_a_long_random_secretInstall the migrate tool used by this project:
go install -tags postgres github.com/golang-migrate/migrate/v4/cmd/migrate@latestVerify the binary is available:
migrate -versionOnce PostgreSQL is running and the .env file is configured, apply migrations and start the API:
./scripts/migrate.sh up
go run ./cmd/apiThis is a fallback for local development and is not the recommended path for this project because the repository already includes a Dockerized workflow.
.
βββ cmd/
β βββ api/ Application entrypoint and route registration
βββ internal/
β βββ config/ Environment and application configuration
β βββ db/ PostgreSQL connection setup
β βββ handlers/ HTTP request handlers
β βββ middleware/ JWT authentication middleware
β βββ models/ User and task data models
β βββ repository/ Database queries and persistence operations
βββ migrations/ Versioned PostgreSQL schema migrations
βββ scripts/ Development and migration scripts
For a bug fix or new feature, create a branch and open a pull request:
git checkout -b <name-of-your-branch>Please keep changes focused, update migrations when the data model changes, and include tests for new behavior where practical. You can also open an issue to report a bug or suggest an improvement.
If this project is useful to you, consider starring the repository and sharing feedback through an issue or pull request.