# Installation & Project Architecture

> A new Goravel project, Postgres and Redis in Docker, the server running.

Goravel series, part 1 of 20. By Krishan Kumar. Published 2026-03-08, Updated 2026-10-24.
Source: https://devchalk.com/goravel/01-installation-project-architecture
Code: https://github.com/krishankumar01/goravel-taskflow/tree/part-01

This is the first part of the [Goravel series](https://devchalk.com/goravel). 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](https://devchalk.com/articles/how-to-read) shows what the code blocks and figures on these pages do.

Clone the finished code for this part:

```bash
git clone https://github.com/krishankumar01/goravel-taskflow
cd goravel-taskflow
git checkout part-01
cp .env.example .env
go run . artisan key:generate
docker compose up -d
```

## The 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.

*TaskFlow's three tables. Point at a line, or tap it, to highlight the two columns it joins.*

tasks.created_by and tasks.assigned_to both reference users.id; assigned_to may be empty. notifications.user_id references users.id, and notifications.task_id references tasks.id.

## Check Go and install the Goravel CLI

You need Go and [Docker](https://docs.docker.com/get-started/get-docker/). Goravel v1.18 needs Go 1.25 or newer. Check what you have:

```bash
$ go version
go version go1.27.3 darwin/arm64
```

If Go is missing or older, install the current release from [go.dev](https://go.dev/dl/).

Goravel has an [installer](https://github.com/goravel/installer) for new projects, like the [Laravel installer](https://laravel.com/docs/12.x/installation). Install it and run it once:

```bash
go install github.com/goravel/installer/goravel@latest
goravel
```

If 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:

```bash
export PATH=$PATH:$(go env GOPATH)/bin
```

## Create the project

The installer creates the project and asks a few questions as it goes:

```bash
goravel new
```

1. **Project name**: `taskflow`.
2. **Version**: pick **[Goravel Lite](https://github.com/goravel/goravel-lite)**. It is a slimmer skeleton where you choose only the facades you need. The full version installs everything.
3. **Module name**: `taskflow`. This is the `module` line in `go.mod`, and the start of every import path, like `taskflow/routes`.

It clones the skeleton and downloads the dependencies. Lite starts with only Artisan, Config and Process, so it then runs [`package:install`](https://www.goravel.dev/getting-started/installation) 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](https://github.com/goravel/gin). [Fiber](https://github.com/goravel/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:

```bash
cd taskflow
```

## The project structure

If you know [Laravel](https://laravel.com), 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.go` is 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 with `web.go`, and we add more in later parts.
- `main.go` is the entry point. It boots the app using `bootstrap.Boot()` and starts the server.

- `taskflow/`
  - **`app/`** *your code: controllers, models, jobs*
    - `facades/` *Route(), Orm(), Cache() and 17 more, one small file each, yours to edit*
    - `http/controllers/` *request handlers; user_controller.go is a sample*
  - `bootstrap/`
    - **`app.go`** *wires the app together: providers, route files, migrations*
    - `providers.go` *the service providers package:install registered*
  - **`config/`** *12 files: app, auth, cache, database, queue… values come from .env*
  - `database/migrations/` *schema changes; Queue's jobs table is already here*
  - `resources/views/` *HTML templates; welcome.tmpl is the page at /*
  - **`routes/`** *route files; Goravel loads only the ones app.go calls*
    - `web.go` *GET / and GET /users*
  - `.env` *your machine's settings: database, Redis, APP_KEY. Not in Git*
  - `docker-compose.yml` *Postgres and Redis, set up in the next section*
  - `go.mod` *module taskflow, Goravel v1.18*
  - **`main.go`** *the entry point: boots the app, then starts the HTTP server*

*The five from the list are in bold.*

## Postgres and Redis with Docker Compose

We chose [Postgres](https://www.postgresql.org) and [Redis](https://redis.io), 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:

```yaml title="docker-compose.yml" t1:db-port=/"(5433):/ t2:redis-port=/"(6380):/ t3:db-login=/POSTGRES_DB: (taskflow)/ t3:db-login=/POSTGRES_USER: (goravel)/ t3:db-login=/POSTGRES_PASSWORD: (secret)/
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](https://docs.docker.com/reference/compose-file/services/#ports) 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](https://docs.docker.com/reference/compose-file/volumes/) keep the data on disk, so stopping or recreating a container does not wipe the database.

*What `host:container` means. Change the mapping, or turn on "Postgres already installed", and see where the app's connection lands.*

With "5433:5432" the container's 5432 answers on your machine's 5433, so DB_PORT=5433 and a local Postgres keeps 5432. With "5432:5432" and nothing else on 5432, DB_PORT=5432 works. With "5432:5432" while a local Postgres holds 5432, docker compose up fails: port is already allocated.

Make sure Docker is running, then start them in the background:

```bash
docker compose up -d
docker ps
```

You 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:

```dotenv title=".env" t1:db-port=/DB_PORT=(5433)/ t2:redis-port=/REDIS_PORT=(6380)/ t3:db-login=/DB_DATABASE=(taskflow)/ t3:db-login=/DB_USERNAME=(goravel)/ t3:db-login=/DB_PASSWORD=(secret)/
DB_HOST=127.0.0.1
DB_PORT=5433
DB_DATABASE=taskflow
DB_USERNAME=goravel
DB_PASSWORD=secret

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=
REDIS_PORT=6380
```

## Run it

With the containers up, start the app:

```bash
$ 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:3000
```

It 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](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](https://www.goravel.dev/digging-deeper/artisan-console), as in Laravel:

```bash
go run . artisan
```

This 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:

```bash t1:db-port=/Port [.]+ (5433)/ t3:db-login=/Database [.]+ (taskflow)/ t3:db-login=/Username [.]+ (goravel)/
$ go run . artisan db:show
  PostgreSQL .................................................................
  Database .......................................................... taskflow
  Host ............................................................. 127.0.0.1
  Port .................................................................. 5433
  Username ........................................................... goravel
  Open Connections ......................................................... 1
  Tables ................................................................... 0
```

It 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.

---

Next: [02. Routing & Responses](https://devchalk.com/goravel/02-routing-response.md)
Series: [Goravel](https://devchalk.com/goravel.md)
