DEV Community

Cover image for 📖 Build a RESTful API on Go: Fiber, PostgreSQL, JWT and Swagger docs in isolated Docker containers

📖 Build a RESTful API on Go: Fiber, PostgreSQL, JWT and Swagger docs in isolated Docker containers

Vic Shóstak on March 22, 2021

Introduction Hello, friends! 😉 Welcome to a really great tutorial. I've tried to make for you as simple step-by-step instructions as pos...
Collapse
 
figaarillo profile image
Axel Leonardi

Thank you so much for taking the time to make this tutorial! 😄

Collapse
 
narven profile image
Pedro Luz • Edited

Really good tutorial. Only one complain.

You should never, ever mix the database layer with http layer. Or any other layer.

type Book struct {
    ID         uuid.UUID `db:"id" json:"id" validate:"required,uuid"`
    CreatedAt  time.Time `db:"created_at" json:"created_at"`
    UpdatedAt  time.Time `db:"updated_at" json:"updated_at"`
    UserID     uuid.UUID `db:"user_id" json:"user_id" validate:"required,uuid"`
    Title      string    `db:"title" json:"title" validate:"required,lte=255"`
    Author     string    `db:"author" json:"author" validate:"required,lte=255"`
    BookStatus int       `db:"book_status" json:"book_status" validate:"required,len=1"`
    BookAttrs  BookAttrs `db:"book_attrs" json:"book_attrs" validate:"required,dive"`
}
Enter fullscreen mode Exit fullscreen mode

db:"id" json:"id" <---- never do this.

Data that you use in JSON "objects" should never be mixed with Models "objects"

Those 2 things work in parallel universes, they should never have contact with each other. One thing is what an frontend application sends to you, another thing is what you put or get from the databases and another thing is what you return from the database and another thing is what you return to the user.

Except on really basic cases, like Todos and Hello worlds that data is always diferent. I advise to take a look at this awesome post:
eltonminetto.dev/en/post/2020-07-0...

It explains in a lot more detail. Of course this does not affect simple applications. But as soon as you start having a few more complexities in your code, those "structs" will fail to provide you what you need.

A more concrete example of what I'm trying to explain would be:

if you signin up a user using email, password. would you use the same "struct" to map it to the database?

a struct for the payload would be something like:

type SignInDTO struct {
   Email string `json:"email" validate:"email"`
   Password string `json:"password" validate:"min=8, max=100"`
}
Enter fullscreen mode Exit fullscreen mode

Does this has any relationship to what what you have in your "user(s)" table? Most likely your "user(s)" table has 15 fields.

Dont mix those.

PS: Also to mention, if you notice I named my struct with DTO (Data Transfer Object) usually is what is called to this kind of "objects" that you can pass arround to your services instead of passing multiple args to your methods.

You service would be much cleaner if you pass:

MyAuthService(ctx context.Context, dto SignInDTO)

instead of

MyAuthService(ctx context.Context, email, password string)

is just a personal opinion.

Collapse
 
koddr profile image
Vic Shóstak

Hi,

db:"id" json:"id" <---- never do this.

Data that you use in JSON "objects" should never be mixed with Models "objects"

This is true for models like authorization or transactional models, where you really need to separate the JSON output through the REST API (as if for the user) and the database query layer (as if our internal).

For example, if we have a User structure where we define what will be output in the profile on the frontend, then it's logical that for the authorization process itself we would need to make a separate UserSignIn structure with just two fields of email and password. That's the only data we would go with a query to the database.

That's where I agree.

But the model in question in the article is a common example of outputting data "as is" after the query. So, I don't really understand why to break DRY and duplicate exactly the same structure in the database layer, if they are identical and were described only to show the principle of work itself?

Especially since there is no consensus on "how to do the structure correctly for Go projects". I adhere to the principle as described in this article, for such "dumb" models, so far I have not encountered a single problem in years of working with Go 🤷

I, by the way, will describe all such things "from the real world of Go development" in detail in my new series of articles Go Fiber by Examples on this blog.

Thanks for the comment, anyway! 👍

Collapse
 
narven profile image
Pedro Luz

Another useful thing from dtos:

  • is that you can reference them in you schema for swagger.
  • also if you work with a monorepo with a frontend project, you can generate Typescript interfaces, so that the frontend developer can use them.
Collapse
 
alexbezverkhniy profile image
Alexander Bezverkhniy • Edited

Great tutorial! Like your style, "less water" )).

I got some issues during migrate installation:

❯ go get github.com/golang-migrate/migrate            
go get: added github.com/golang-migrate/migrate v3.5.4+incompatible
Enter fullscreen mode Exit fullscreen mode

I have go version go1.17.1 and linux/amd64 (Arch linux)

BTW I've fixed that by building it from the source.

Just thoughts... I guess you can simplify "Docker related steps" by using docker-compose.

Collapse
 
koddr profile image
Vic Shóstak • Edited

Hi,

Thanks for reply!

I got some issues during migrate installation [...]

If you have some problems with local installation golang-migrate/migrate, try Docker-way of them, like this:

docker run \
  -v ${PWD}:/migrations \
  --network host \
  migrate/migrate \
    -path=/migrations/ \
    -database postgres://localhost:5432/database up
Enter fullscreen mode Exit fullscreen mode

[...] I guess you can simplify "Docker related steps" by using docker-compose.

It's cool that you noticed this, but I did it on purpose to show the full setup for beginners... 😉 Also, I personally like to use Ansible playbooks instead of docker-compose to deploy the project to live servers (if that's what you want to roll out), but that would be too much for an already large tutorial.

Collapse
 
alexbezverkhniy profile image
Alexander Bezverkhniy

Thank you Vic! Gonna try that.

Collapse
 
phtremor profile image
PHTremor

the migrate cli command is giving me an error,
here is the command and the error

migrate -path $(pwd)/platform/migrations -database "postgres://postgres:password@localhost/postgres?sslmode=disable" up
error: dial tcp [::1]:5432: connect: connection refused

when i leave $(PWD) in uppercase i get:

bash: PWD: command not found
error: open /platform/migrations: no such file or directory

Collapse
 
minhblues profile image
MinhBLues

you need change localhost to dev-postgres when use docker

Collapse
 
koddr profile image
Vic Shóstak

Hi,

What OS/platform do you use for run? Are you sure, that Docker container with PostgreSQL is up on this port and on the same Docker network?

Collapse
 
phtremor profile image
PHTremor

I'm using parrot os/linux-distro. I somehow cant get to install migrate with the go toolchain, it gives me an error:

go get: module github.com/golang-migrate/migrate@upgrade found (v3.5.4+incompatible), but does not contain package github.com/golang-migrate/migrate/cmd/migrate

the unversioned command to install couldn't work too.

so i used the alternative docker run -v ... usage command to migrate, and it worked. i suggest i will just stick to using migrations with docker.

Thank you though.

Thread Thread
 
cells200 profile image
Cells

Hi PHTremor,
Do you mind to share your command on the docker run -v ...usage?
I use this command and it cannot find the folder.
Thanks, Simon

Thread Thread
 
cells200 profile image
Cells

Hi PHTremor,
Never mind. I got it working by using the "migrate" command as stated by Vic.
migrate \
-path $(PWD)/platform/migrations \
-database "postgres://postgres:password@localhost/postgres?sslmode=disable" \
up

Collapse
 
phtremor profile image
PHTremor • Edited

I have 2 questions:

  1. How can I test the links - creating a new book - using postman? I am generating new tokens, but failing to create a new book, get it, and deleting

  2. How can i perform the tests - the testify - implementation; steps if possible

Collapse
 
soundeasy profile image
Kevin

Hi, this article really helped me learning gofiber, but i wonder about where to store my jwt key after it generated. First, i want to create android apps and consume my go rest api. As you mentioned in the end of this article, "Add a standalone container with Redis (or similar) to store the sessions of these authorized users". So i better store the jwt into redis? What is disadvantage when i store it at fiber c.Locals or c.Session?

Collapse
 
dhinojosac profile image
Diego Hinojosa Cordova • Edited

Very useful tutorial.
I had an erro when run make docker.run , especially in migrate.up:
If anyone can help me, thanks in advance!


migrate -path C:/Users/dhinojosac/go/src/github.com/dhinojosac/tgc-api-template/tutorial-go-fiber-rest-api/platform/migrations -database "postgres://postgres:password@127.0.0.1/postgres?sslmode=disable" up
error: pq: password authentication failed for user "postgres"
make: *** [Makefile:25: migrate.up] Error 1


Enter fullscreen mode Exit fullscreen mode
Collapse
 
koddr profile image
Vic Shóstak • Edited

Hi,

Check your password for local DB user postgres, as error was said: pq: password authentication failed for user "postgres".

See Makefile's line 6 for more info.

Collapse
 
cells200 profile image
Cells

Hi Vic,
Sorry for a silly question. When you wrote: "Let's run Docker containers, apply migrations and go to 127.0.0.1:5000/swagger/index.html:", what are the command to "Run Docker container" and "apply migrations"?
Thanks,
Simon

Collapse
 
cells200 profile image
Cells

Never mind! Found it in the README.md.

make docker.run

Collapse
 
nintran52 profile image
Nin Tran

This is naming for rest api restfulapi.net/resource-naming/