API Rest con Go (Golang) y PostgreSQL Parte 1
Tags: go, rest, api, opensource
Published: 2020-04-14 00:05:28

Desarrollo de una API Rest sencilla con [Go](https://golang.org/) y el motor de base de datos [PostgreSQL](https://www.postgresql.org/).

## Requisitos:

* Conocimientos básicos sobre el lenguaje [go](https://golang.org/).
* Conocimiento sobre HTTP y REST recomendable.
* [Go](https://golang.org/dl/) en su versión 1.11 o superior.
* [PostgreSQL](https://www.postgresql.org/download/) en su versión 9.6 o superior.
* Conocimientos básicos de SQL deseables.
* Ganas de aprender.

## Que vamos a desarrollar

Crearemos un API Rest para un microblog, estilo [twitter](https://twitter.com/notifications) en su versión mas básica. Con un modelo de usuarios y uno de publicaciones. Con un [CRUD](https://en.wikipedia.org/wiki/Create%2C_read%2C_update_and_delete) completo en cada caso, autenticación con [JSON Web Tokens](https://jwt.io/), creación, uso de middlewares y asegurar contraseña por con bcrypt. ¿Suena interesante? ¡Entonces vamos!

### Indice

* **Parte 1 - Preparación**
* [Parte 2 - Modelos](https://dev.to/orlmonteverde/api-rest-con-go-golang-y-postgresql-parte-2-137l)
* [Parte 3 - Base de datos](https://dev.to/orlmonteverde/api-rest-con-go-golang-y-postgresql-parte-3-29eb)
* [Parte 4 - Manejo de peticiones](https://dev.to/orlmonteverde/api-rest-con-go-golang-y-postgresql-parte-4-4i31)
* [Parte 5 - Middlewares y JWT](https://dev.to/orlmonteverde/api-rest-con-go-golang-y-postgresql-parte-5-3p5g)


### Crearemos una carpeta para nuestro proyecto

Pueden darle el nombre de su preferencia a la carpeta y colocarla donde les parezca adecuado, en mi caso la llamare **go-postgres-microblog** para identificarla con facilidad. También puede crearla desde su gestor de archivos, a mi me gusta usar la terminal y sentirme hacker. 

```
mkdir go-postgres-microblog && cd $_
```

### Iniciando un modulo de go

Una vez dentro de la carpeta deseada, desde el terminal escribimos el siguiente comando

```
go mod init github.com/orlmonteverde/go-postgres-microblog
```
Eso creara un archivo llamado go.mod y el contenido de la carpeta deberia lucir de esta manera

```
.
└── go.mod
```
Si no han trabajando anteriormente con los [Go Modules](https://blog.golang.org/using-go-modules), les recomiendo revisar [este articulo](https://blog.golang.org/using-go-modules), si no desean hacerlo pueden simplemente seguir mis pasos o trabajar en su [gopath](https://golang.org/doc/gopath_code.html).

### Estructura del proyecto

Para este proyecto estaremos usando de forma un poco libre la estructura propuesta por [golang-standards](https://github.com/golang-standards/project-layout), no es la única estructura posible, pero es una que me resulta particularmente cómoda.

#### Directorios
*  **cmd**: para construir los ejecutables (aquí tendríamos tantos directorios con paquetes *main* como requiera el proyecto).
* **pkg**: todos los paquetes que queremos compartir y que puedan ser reutilizables por otros proyectos.
* **internal**: paquetes que solo competen al proyecto actual y que no queremos que sean utilizados fuera del mismo.

## Creando directorios

Creamos los directorios de trabajo en la raíz del proyecto.

```bash
mkdir cmd pkg internal
```

Deberíamos tener una estructura similar a la siguiente.


```bash
.
├── cmd
├── go.mod
├── internal
└── pkg
```

Ahora dentro de **cmd** crearemos un directorio que contendrá el punto de inicio de nuestra aplicación, la llamare **microblog**, pueden llamarla como prefieran, pero para evitar confusiones seria recomendable que mantuvieran los nombres. De hecho, para evitar repetirme constantemente de ahora en adelante todos los nombres utilizados son recomendados, pero no obligatorios, a memos que se indique lo contrario.

```bash
mkdir cmd/microblog
```

Creamos un archivo llamado **main.go** dentro del directorio **microblog** .

```bash
touch cmd/microblog/main.go
```

El contenido de este archivo por el momento sera el siguiente

```go
package main

import "fmt"

func main() {
	fmt.Println("Hello world!")
}
```
Ahora vamos a ejecutar nuestro programa para asegurarnos de que esta todo correcto hasta este punto. Desde la raíz del proyecto, ejecutamos el comando.

```bash
go build ./cmd/microblog
```

Ejecutado el comando deberíamos tener un binario llamado **microblog** (o microblog.exe si estas en windows). El cual podemos ejecutar.

```bash
./microblog
```
Y obtener una salida como esta.

```bash
Hello world!
```

Todo correcto hasta este punto, así que, comencemos a desarrollar nuestra API.

## Modulo server

Lo primero que haremos sera crear un directorio **server** dentro del directorio **internal**, y luego crearemos un archivo llamado **server.go** dentro del mismo.

```bash
mkdir internal/server

touch internal/server/server.go
```

El contenido del archivo **server.go** sera inicialmente una estructura llamada **Server** que contendrá un puntero a la estructura *http.Server* de la biblioteca estándar.

```go
// internal/server/server.go
package server

import "net/http"

// Server is a base server configuration.
type Server struct {
	server *http.Server
}
```

En este punto vamos a incluir, un paquete de terceros para manejar las rutas, en este caso [chi](http://github.com/go-chi/chi), mediante **go get**, ejecutando el siguiente comando.

```bash
go get github.com/go-chi/chi
```

Hecho esto, se debió actualizar el archivo **go.mod**, ademas de crearse un archivo llamado **go.sum**, no nos preocuparemos de ellos porque no los vamos a editar, no directamente, pero si revisan el **go.mod** deberían ver el paquete **chi** que acabamos de descargar como un paquete requerido.

Ahora, regresando al archivo **server.go**, agregamos una función, que llamaremos **New** que recibe un *string* llamado **port** y retorna un puntero a la estructura **Server** y un *error*, dentro de esta función configuraremos el servidor y posteriormente lo retornaremos.

Desde el paquete **chi** estamos usando la función **NewRouter**, para obtener un nuevo *Router* y lo agregamos como **Handler** en la estructura **http.Server**, ademas de establecer el **Addr** con el **port** que recibe la función y limitar el tiempo de lectura y escritura a 10 segundos, para esto es necesario importar el paquete **time** de la biblioteca estándar.

```go
// […]

// New inicialize a new server with configuration.
func New(port string) (*Server, error) {
	r := chi.NewRouter()

	serv := &http.Server{
		Addr:         ":" + port,
		Handler:      r,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	server := Server{server: serv}

	return &server, nil
}
```

Y ahora agregamos un par de métodos a la estructura **Server**, uno que llamaremos **Close** que utilizaremos para cerrar recursos antes de terminar con la ejecución del programa (por ejemplo, la conexión con la de base de datos, aunque de momento este método estará vacío) y uno llamado **Start**, en el que activamos el servidor.

```go
// […]

// Close server resources.
func (serv *Server) Close() error {
	// TODO: add resource closure.
	return nil
}

// Start the server.
func (serv *Server) Start() {
	log.Printf("Server running on http://localhost%s", serv.server.Addr)
	log.Fatal(serv.server.ListenAndServe())
}
```

Volvamos al archivo **main.go**, para requerir el paquete **server** que acabamos de crear y pongamos a correr el servidor.

```go
package main

import (
	"os"
	"os/signal"

	"log"

	"github.com/orlmonteverde/go-postgres-microblog/internal/server"
)

func main() {
	serv, err := server.New("8000")
	if err != nil {
		log.Fatal(err)
	}

	// start the server.
	go serv.Start()

	// Wait for an in interrupt.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	<-c

	// Attempt a graceful shutdown.
	serv.Close()
}
```

Como se puede ver, estamos ejecutando el servidor dentro de una *goroutine*. Pero si lo dejamos así la función **main** llegara a su fin y la ejecución del programa terminara, y no es lo que buscamos.

Por ese motivo se agrega un *channel* que queda en espera de recibir un evento del sistema, una señal de interrupción (CTRL + C), asi el programa se mantendrá en ese punto hasta que ese evento ocurra y una vez que suceda ejecutara el resto de las instrucciones, en nuestro caso el método **Close** de nuestro **Server**.

Quizás te estés preguntando porque ejecutar el servidor en un *goroutine* separada, de no hacerlo no seria necesario crear el *channel* y demás. Es correcto, pero no tendríamos oportunidad de ejecutar otras instrucciones antes de que el programa finalice. Ahora ejecutamos nuevamente el programa.

```go
go build ./cmd/microblog && ./microblog
```

Si vamos a [http://localhost:8000](http://localhost:8000) en nuestro navegador deberíamos ver un mensaje de “404 page not found”, que puede resultar un poco decepcionante, pero esta bien, significa que tenemos corriendo nuestro servidor, aunque aun no configuramos ninguna ruta, eso lo haremos mas adelante. 

## Variables de entorno

En el paso anterior, al inicializar el servidor pasamos el puerto directamente como un *string*, esto funciona bien, pero si quisiéramos cambiar el servidor tendríamos que modificar el código y volver a compilar, ademas de que en algunos casos no tenemos control sobre el puerto con el que funcionara el servidor, como es el caso de plataformas como [Heroku](https://heroku.com).

Ademas de otras variables de configuración, que muy posiblemente queramos cambiar según el entorno en el que este corriendo nuestro programa, como la [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) de la base de datos. En esos casos trabajar con variables de entorno podría ser una mejor opción, y eso haremos a continuación.

Primero crearemos un archivo llamado .env en la raíz de nuestro proyecto. Dentro de este archivo agregaremos las variables de entorno de forma <NOMBRE>=<VALOR>. En este caso solo nos interesa el puerto.

```bash
# .env
PORT=8000

```
Ahora solo debemos leer el valor de la variable de entorno **PORT** y pasarlo a la función **New**, así hagamos unos pequeños cambios al inicio de la función **main** en el archivo **main.go**.

```go
// cmd/microblog/main.go
// […]
func main() {
	port := os.Getenv("PORT")
	serv, err := server.New(port)
        // [...]
}
```

Con esto podríamos cargar manualmente nuestras variables de entorno antes de ejecutar el programa y problema resulto, pero es repetitivo, aburrido y fácil de olvidar. Vamos a realizar esto de manera automática por medio de otro paquete de terceros, al igual que con **chi** tendremos que descargarlo con el comando **go get**. En este caso el paquete es [GoDotEnv](https://github.com/joho/godotenv)

```bash
go get github.com/joho/godotenv
```

Ahora que hemos descargado el paquete, regresamos al archivo **main.go** para utilizarlo, aunque existen varias formas de trabajar con este paquete y los invito a revisar su [documentación](https://pkg.go.dev/github.com/joho/godotenv?tab=doc), nosotros usaremos la mas sencilla, simplemente importando el paquete **autoload**. De esta forma, nuestro archivo **main.go** debería lucir similar al siguiente fragmento de codigo.

```go
package main

import (
	"os"
	"os/signal"

	"log"

	"github.com/orlmonteverde/go-postgres-microblog/internal/server"

	_ "github.com/joho/godotenv/autoload"
)

func main() {
	port := os.Getenv("PORT")
	serv, err := server.New(port)
	if err != nil {
		log.Fatal(err)
	}

	// start the server.
	go serv.Start()

	// Wait for an in interrupt.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	<-c

	// Attempt a graceful shutdown.
	serv.Close()
}
```

Con estos cambios, podemos modificar el valor de la variable de entornos tantas veces como sean necesarias y al ejecutar el programa este tomara el nuevo valor. Los invito a hacer la prueba.

Por ahora lo dejaremos hasta aquí, en el [siguiente articulo](https://dev.to/orlmonteverde/api-rest-con-go-golang-y-postgresql-parte-2-137l) continuaremos con el desarrollo de nuestra API. Les comparto el [repositorio](https://github.com/orlmonteverde/go-postgres-microblog/tree/v1.0.0) con el código del proyecto hasta el momento.

Realmente espero que el contenido les sea de utilidad, y cualquier duda o sugerencia, por favor, déjenla en los comentarios y les responderé tan pronto como me resulte posible. Saludos y gracias por leer.

![konosuba](https://media.giphy.com/media/t6AXtEOV8R0nS/giphy.gif)