Deep Dive into Go: Crafting a CRUD RESTful API Without Frameworks
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