Deep Dive into Go: Crafting a CRUD RESTful API Without Frameworks

Benediktus Satriya
5 min readMar 21, 2024

--

Introduction

In today’s tech landscape, building RESTful APIs is a common task for developers. While there are numerous libraries and frameworks available to streamline this process, sometimes it’s beneficial to go back to basics and leverage the native capabilities of a programming language. In this tutorial, we’ll explore how to build a lightweight RESTful API in Go without relying on any external packages. By embracing Go’s simplicity and power, we’ll craft a clean and efficient API from scratch.

Prerequisites

Before we dive in, make sure you have Go installed on your machine. You can download and install it from the official Go website (golang.org). Basic familiarity with Go syntax and concepts like structs, functions, and HTTP handling will be helpful but not required. Our application will utilize a PostgreSQL database to store data. Ensure you have PostgreSQL installed on your machine.

Setting Up Your Project

To get started, let’s create a new directory for our project. Open your terminal and run the following commands:

mkdir simple-native-api
cd simple-native-api
go mod init simple-native-api

This will create a go.mod file in your project directory, enabling Go modules for managing dependencies.

Setup Database

Creating Table in PostgreSQL:

Now, let’s create a table in your PostgreSQL database. We’ll create a simple table named products with two columns: id , name , and price

CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price INTEGER NOT NULL,
stock INTEGER NOT NULL
);

Great!! After created, now in db looks like this.

When building web applications in Go, establishing a connection to a PostgreSQL database is a common requirement. In this guide, we’ll walk through the process of setting up a database connection using the PostgreSQL driver in Go.

database.go

Setting Up Database Connection in Go using PostgreSQL Driver

package main

import (
"database/sql"
_ "github.com/lib/pq"
)

func NewDB(dbDriver string, dbSource string) *sql.DB {
db, err := sql.Open(dbDriver, dbSource)

if err != nil {
panic(err)
}

return db
}

First, we need to import the necessary packages in our Go code. We’ll import the database/sql package, which provides the interface for working with SQL databases, and the PostgreSQL driver package, which allows Go to communicate with PostgreSQL databases.

service.go

First, let’s establish a connection to our PostgreSQL database. We’ll use the database/sql package along with the PostgreSQL driver package (github.com/lib/pq) to interact with the database.

package main

import (
"database/sql"
"net/http"
)

type Service struct {
db *sql.DB
}

func NewService(db *sql.DB) *Service {
return &Service{db: db}
}

In the Service struct, we'll store the database connection, and we'll define a constructor function NewService to initialize the service with the database connection.

Next, let’s implement the CRUD operations for our API. We’ll create methods within our Service struct to handle each operation.

func (s *Service) CreateProduct(writer http.ResponseWriter, request *http.Request) {
// Implementation for creating a new product
}

func (s *Service) GetProducts(writer http.ResponseWriter, request *http.Request) {
// Implementation for fetching all products
}

In the CreateProduct method, we'll read the request body to get the product data, insert it into the database, and return the created product.

In the GetProducts method, we'll query the database to fetch all products and return them as a response.

Designing the Router

router.go

Our router will be represented by a struct containing a map of routes, where each route corresponds to a specific HTTP method. We’ll define methods to add routes and handle incoming HTTP requests.

package main

import (
"database/sql"
"net/http"
)

type router struct {
routes map[string]map[string]http.HandlerFunc
}

func NewRouter(db *sql.DB) *router {
router := &router{
routes: make(map[string]map[string]http.HandlerFunc),
}

service := NewService(db)

router.addRoute("POST", "/products", service.CreateProduct)
router.addRoute("GET", "/products", service.GetProducts)

return router
}

In the NewRouter function, we initialize a new router instance and associate routes with their corresponding HTTP methods. We'll use this function to set up our router when initializing our API service.

Implementing HTTP Handlers

func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if handlers, ok := r.routes[req.URL.Path]; ok {
if handler, methodExists := handlers[req.Method]; methodExists {
handler(w, req)
return
}
}
http.NotFound(w, req)
}

In the addRoute method, we simply add a new route to our router's map of routes, associating it with the specified HTTP method.

func (r *router) addRoute(method, path string, handler http.HandlerFunc) {
if r.routes[path] == nil {
r.routes[path] = make(map[string]http.HandlerFunc)
}
r.routes[path][method] = handler
}

main.go

First, we’ll initialize a connection to our PostgreSQL database using the NewDB function. We'll pass the database driver and connection string as arguments to this function.

db := NewDB("postgres", "user=root dbname=tutorial password=root sslmode=disable")

Next, we’ll configure our HTTP router using the NewRouter function. We'll pass the database connection (db) to this function to integrate it with our router.

router := NewRouter(db)

Now that we have our database connection and router set up, we can start the HTTP server. We’ll configure the server to listen on port 8080 and use our router as the request handler. Finally, we’ll start the HTTP server by calling the ListenAndServe method on our http.Server instance.

server := http.Server{
Addr: ":8080",
Handler: router,
}

fmt.Println("listening to port 8080...")
err := server.ListenAndServe()
if err != nil {
panic(err)
}

This starts the HTTP server and listens for incoming requests on port 8080. If any errors occur during server startup, we'll panic and print the error message.

Testing the API using Postman

Awesome! Now, for the final step, let’s create products using the REST API we’ve constructed with the POST method at /products. Here’s the JSON data for the request body:

{
"name":"indomie goreng",
"price":3000,
"stock":10
}

To retrieve all products, simply use the GET method at /products. Voilà! You’ll receive all the product data.

Conclusion

In conclusion, we’ve demonstrated how to create a RESTful API service entirely without relying on any frameworks or libraries. Leveraging the simplicity and speed of development offered by native Go, we’ve achieved excellent results. This journey showcases the power and versatility of Go in crafting efficient and scalable web services. With Go, the possibilities are endless, and the development process remains both straightforward and efficient.

Full example code https://github.com/benebobaa/simple-native-crud

--

--