First, you'll need to start the postgres database:
sudo docker-compose -f docker-compose-dev.yml upTo run the server, simply cd into the server directory and run:
go run cmd/server/main.goThe server will run on port 8080, and postgres database should be at 5432.
You can also connect to it with psql:
sudo docker exec -it go-starter_dev_db_1 psql -U adminIf you need to reinit the database with init-db.sql, you'll need to clean it:
sudo docker-compose -f docker-compose-dev.yml down --volumes
In development, the Dockerfile builds the server and starts it on port 80. To start both the server and postgres database, run:
sudo docker-compose upThe structure of this projects strictly follows Clean Architecture rules.
The entry point of the server is located in:
cmd
|-server
|-main.go
This is where you should connect to the database (and remember to close it). It's also the place where you should get all your environmental variables for use later.
Domain is where you should define your structs and interfaces.
pkg/domain/user.go:
package domain
type User struct {
Email string `json:"email"`
PasswordHash string `json:"-"`
}
type UserRepo interface {
GetUserByEmail(email string) (*User, error)
CreateUser(email, passwordHash string) error
UserExists(email string) bool
}
type UserService interface {
GetUser(email string) (*User, error)
Register(email string, password string) (*User, error)
Login(email string, password string) (*User, error)
}A repository is something that saves/gathers data, such as a database. It uses models from the domain such as User.
func (r *repo) GetUserByEmail(email string) (*domain.User, error) {
u := domain.User{}
err := r.Conn.
QueryRow("SELECT email,password_hash FROM users WHERE email=$1", email).
Scan(&u.Email, &u.PasswordHash)
if err != nil {
return nil, domain.ErrNotFound
}
return &u, nil
}A service can use the repositories and models. It performs more real life use case operations such as Login that might require some logic.
func (s *service) Login(email string, password string) (*domain.User, error) {
user, err := s.repo.GetUserByEmail(email)
if err != nil {
return nil, err
}
if !matchesHash(password, user.PasswordHash) {
return nil, domain.ErrCredentials
}
return user, nil
}Delivery is a vague name I decided to use for "endpoints" such as REST API or gRPC. It's a set of functions that use services to interface with the endpoints. Here is an example:
func (h *handler) getUser(w http.ResponseWriter, r *http.Request) {
email := mux.Vars(r)["email"]
user, err := h.userService.GetUser(email)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}