This is the first part of the Goravel series. We are going to build TaskFlow, a task API for teams, and learn Goravel as we go. In this part we plan the database, create the project, start Postgres and Redis in Docker, and get the server running. How to Read DevChalk shows what the code blocks and figures on these pages do.
CodeClone the finished code for this part
git clone https://github.com/krishankumar01/goravel-taskflowcd goravel-taskflowgit checkout part-01cp .env.example .envgo run . artisan key:generatedocker compose up -dThe database design
Before we write any code, let’s look at the data. The app needs three tables.
users holds everyone who can log in. Next to the name, email and password, it has a role, which is either manager or member. We use this column for permissions later. A manager can create, edit and delete tasks. A member can only change the status of tasks assigned to them.
| column | type | notes |
|---|---|---|
id |
bigint | primary key |
name |
string | |
email |
string | unique |
password |
string | hashed, never plain |
role |
enum | manager or member, default member |
tasks is the main table. A task has a title, a description, an optional due date and a status that is one of pending, in-progress or done. It links to the users table twice. created_by is the manager who made the task, and assigned_to is the person who has to finish it. The permission rules use both.
| column | type | notes |
|---|---|---|
id |
bigint | primary key |
created_by |
bigint | references users.id |
assigned_to |
bigint | references users.id, nullable |
title |
string | |
description |
text | |
status |
enum | pending, in-progress, done, default pending |
due_at |
timestamp | nullable, when the task is due |
notifications tells a user that something changed. When a task changes, for example when it is assigned to someone, that person gets a row here. read_at is null until the user has seen it.
| column | type | notes |
|---|---|---|
id |
bigint | primary key |
user_id |
bigint | references users.id |
task_id |
bigint | references tasks.id |
message |
string | |
read_at |
timestamp | nullable, null means unread |
So a user can create many tasks, be assigned many tasks, and have many notifications.
Check Go and install the Goravel CLI
You need Go and Docker. Goravel v1.18 needs Go 1.25 or newer. Check what you have:
go versiongo version go1.27.3 darwin/arm64If Go is missing or older, install the current release from go.dev.
Goravel has an installer for new projects, like the Laravel installer. Install it and run it once:
go install github.com/goravel/installer/goravel@latestgoravelIf you see a help screen, it worked. If you see command not found, the Go binary directory is not on your PATH. Add this to your shell config (~/.zshrc or ~/.bashrc), then restart the terminal:
export PATH=$PATH:$(go env GOPATH)/binCreate the project
The installer creates the project and asks a few questions as it goes:
goravel new- Project name:
taskflow. - Version: pick Goravel Lite. It is a slimmer skeleton where you choose only the facades you need. The full version installs everything.
- Module name:
taskflow. This is themoduleline ingo.mod, and the start of every import path, liketaskflow/routes.
It clones the skeleton and downloads the dependencies. Lite starts with only Artisan, Config and Process, so it then runs package:install and asks which facades to add. Press Space to select one and Enter when you are done.
Each facade you pick also installs the facades it depends on, so these four are enough for TaskFlow:
- Route, to define API routes. It also installs Log, Cache, Hash, Http, RateLimiter, Session, Storage, Validation and View.
- Auth, for login. It also installs Orm and Schema.
- Gate, for authorization rules.
- Queue, for background jobs. It also installs DB.
Some facades need a driver, and the installer asks for each one:
- Cache: Redis. The memory driver keeps data inside the process and loses it on every restart.
- Session: Redis, the same as Cache. File works too.
- Storage: Local. Files on disk are enough until we deploy.
- Route: Gin. Fiber is also supported, and your code stays almost the same with either.
- Database: Postgres, for Orm and DB. MySQL, SQL Server and SQLite are also supported.
- Queue: Redis. Sync runs a job right away, inside the request. Database stores jobs in a table. Redis is the usual choice for a real queue.
When it finishes, go into the project:
cd taskflowThe project structure
If you know Laravel, the layout will look familiar.
app/is the application: controllers, models, jobs, services. It also holds the facades. They are plain files in your project, so you can add your own. Most of our code goes here.bootstrap/app.gois startup: service providers, route registration, migrations.config/has a file per area: app, auth, cache, database and so on. You rarely edit these directly. They read their values from.env.routes/holds the route files. It starts withweb.go, and we add more in later parts.main.gois the entry point. It boots the app usingbootstrap.Boot()and starts the server.
taskflow/app/your code: controllers, models, jobsfacades/Route(), Orm(), Cache() and 17 more, one small file each, yours to edithttp/controllers/request handlers; user_controller.go is a sample
bootstrap/app.gowires the app together: providers, route files, migrationsproviders.gothe service providers package:install registered
config/12 files: app, auth, cache, database, queue… values come from .envdatabase/migrations/schema changes; Queue’s jobs table is already hereresources/views/HTML templates; welcome.tmpl is the page at /routes/route files; Goravel loads only the ones app.go callsweb.goGET / and GET /users
.envyour machine’s settings: database, Redis, APP_KEY. Not in Gitdocker-compose.ymlPostgres and Redis, set up in the next sectiongo.modmodule taskflow, Goravel v1.18main.gothe entry point: boots the app, then starts the HTTP server
Postgres and Redis with Docker Compose
We chose Postgres and Redis, so we need both running. The app itself runs with Go on your machine, not inside Docker, because restarts are faster that way. Docker runs only the two services.
The skeleton comes with a docker-compose.yml. Replace what is in it with the two services:
services: postgres: image: postgres:15-alpine restart: always environment: POSTGRES_DB: taskflow POSTGRES_USER: goravel POSTGRES_PASSWORD: secret ports: - "5433:5432" volumes: - postgres_data:/var/lib/postgresql/data
redis: image: redis:alpine restart: always ports: - "6380:6379" volumes: - redis_data:/data
volumes: postgres_data: redis_data:The port mapping is host:container. I already have Postgres on 5432 and Redis on 6379 locally, so I map 5433 and 6380 on the host to the standard ports inside the containers. If you have nothing running on those ports, 5432:5432 and 6379:6379 are simpler. If you use those mappings, set DB_PORT=5432 and REDIS_PORT=6379 in .env in the next section.
The named volumes keep the data on disk, so stopping or recreating a container does not wipe the database.
host:container means. Change the mapping, or turn on "Postgres already installed", and see where the app's connection lands.Make sure Docker is running, then start them in the background:
docker compose up -ddocker psYou should see two containers, taskflow-postgres-1 and taskflow-redis-1. The names start with your folder name, so if you cloned the repo they start with goravel-taskflow-. Both should have a STATUS of Up, and the PORTS column should show 0.0.0.0:5433->5432/tcp and 0.0.0.0:6380->6379/tcp, or whatever ports you mapped.
Environment configuration
The installer already made a .env file, and the app reads its settings from it. Change these lines to match docker-compose.yml and leave the rest of the file as it is:
DB_HOST=127.0.0.1DB_PORT=5433DB_DATABASE=taskflowDB_USERNAME=goravelDB_PASSWORD=secret
REDIS_HOST=127.0.0.1REDIS_PASSWORD=REDIS_PORT=6380Run it
With the containers up, start the app:
go run .
INFO Processing jobs from [redis] connection and [default] queue GET|HEAD / ................................... taskflow/routes.Web.func1 STATIC public ........................................................ GET|HEAD users ... taskflow/app/http/controllers.(*UserController).Index
Showing [3] routes[HTTP] Listening on: http://127.0.0.1:3000It builds the app from main.go, starts a queue worker on Redis and listens on port 3000. It also prints the routes the app has. These three come with the skeleton. Open http://localhost:3000 to see the Goravel welcome page. Press Ctrl+C to stop it.
Goravel has a command line for jobs like migrations and generating code, called Artisan, as in Laravel:
go run . artisanThis lists all the commands, such as db commands, make commands that generate code, and migrate commands.
The server starts even if the database settings are wrong, so check Postgres with one of the db commands:
go run . artisan db:show PostgreSQL ................................................................. Database .......................................................... taskflow Host ............................................................. 127.0.0.1 Port .................................................................. 5433 Username ........................................................... goravel Open Connections ......................................................... 1 Tables ................................................................... 0It shows the database the app connects to. There are no tables yet. If .env does not match docker-compose.yml, you get connection refused instead.
TaskFlow now runs, with Postgres and Redis behind it. It has no routes of its own yet.
git checkout part-01 Browse the code ↗Download zip