Implementing a Web Service With Go and Fiber

Written by alexeysutyagin | Published 2022/10/20
Tech Story Tags: go | fiber | web-development | website-development | software-development | go-programming-language | framework | http-requests

TLDRThe principles of web development are similar for all web frameworks. Let’s implement the most straightforward web server with a Fiber framework to understand its appearance. Fiber is minimalistic but fast and contains all the necessary tools for implementation. We don't need a port or a domain if we are already inside the server to send our request for processing. We only need the path from the source address and the method used in the request. In the last line of our `main` function, we specify which server's port will be handled.via the TL;DR App

The principles of web development are similar for all web frameworks. Let’s learn the basics of web development with the help of Go programming language and Fiber framework and write the most uncomplicated web service.

What reasons for the existence of frameworks?

Every popular web service is based on an exchange between clients and servers. Constantly repeating request processing and responses returning includes many elements similar to most sites.

We don’t want to develop similar tools for every new website. We want to create reused tools for similar logic and don’t invent something new each time. Frameworks were designed for those purposes. They included mechanisms for standard web development issues. Thanks to frameworks, we can’t faster solve business tasks and almost skip low-level technical questions.

Requests and responses

Most modern web frameworks are based on the response-request paradigm, and it doesn’t depend on programming language and realization peculiarities.

Let’s implement the most straightforward web server with a Fiber framework to understand its appearance. Fiber is minimalistic but fast and contains all the necessary tools for implementation. We can find installation instructions on the site.

Hello, World!

Let’s write the simplest application with Fiber. Traditionally it will be a web server that returns Hello, World! Create the file server.go with the following content:

package main 

import "github.com/gofiber/fiber/v2"

func main() {
	app := fiber.New()

	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("Hello, World!")
	})

	app.Listen(":3000")
}

Run the application using the command in the terminal

go run server.go

In the browser, enter http://localhost:3000 and see the text Hello, World!

Let's take apart the application and see how the server understands what to do, and at the same time, let's get to know one crucial concept: routing.

  1. In this piece of code, we create a package called main. Every Go package has a main function, and our code is no exception.
  2. Import the required Fiber framework module.
  3. At the beginning of the main function, we create a new instance of our web server. It will serve requests and do the work.
  4. The next step is to process the route /. Internet requests have an address or URL. We don't need a port or a domain if we are already inside the server to send our request for processing. We only need the path from the source address and the method used in the request.
  5. We'll look at the other options in more detail later, but for now, let's assume that the server will process a GET request with the shortest path / and send a string in response using the SendString context method.
  6. A parameter with a pointer to the context structure is passed to the handler function. Although the design and tips are specific to our framework, the notion of a context bound to a request is universal. The context of a request contains the necessary information and methods for handling the current request.
  7. Components of response and query objects - headers with meta-information
  8. In the last line of our main function, we specify which server's port will be handled. Here it is 3000, but any port will do. You can run more than one instance of the same server on different ports.

We have written the processing of the request at /, but how to go further and process the rest of the requests. After all, we want our server to be able to do something else.

More complex handler

Every request that comes to the server has an address. For our server, while it is running locally on our machine, the address looks like this: http://localhost:3000/resource. The browser already knows that our server is running at localhost:3000, so we don't handle that part inside, but we handle the resource part. If we send a request from the address bar of the browser - the GET method is used, so inside our framework, we repeat the address handler / that we already have. It will look like this:

app.Get("/resource", func(c *fiber.Ctx) error {
	return c.SendString("Desired resource")
})

Restart the server, and by entering the link in the browser, we get the result - the string Desired resource.

As you can see from the example, the first parameter of the function with the name of the HTTP request method is responsible for handling a specific path in Fiber. The handler function, specified by the second parameter, is accountable for the result returned on a particular request. Matching the path and the handler within the web server is called routing, and it's a mandatory step in every web framework.

Dynamic address

Addresses can be dynamic and contain parameters such: As user ID or the movie's name. There is also a ready-to-use functionality for this inside Fiber. Let's implement a request handler with the following parameter http://localhost:3000/resource/resource_id

app.Get("/resource/:id", func(c *fiber.Ctx) error {
    return c.SendString("Desired resource = " + c.Params("id"))
})

A colon in front of the parameter name will allow us to get this value in the request handler, which we demonstrate by adding the resource ID to the response. There can be more than one parameter, which allows you to implement complex routing.

This is what a route and a request handler with two parameters http://localhost:3000/user/8427/book/HarryPotterandtheChamberofSecrets would look like:

app.Get("/user/:id/book/:title", func(c *fiber.Ctx) error {
    return c.SendString("Desired book is " + c.Params("title") + " from user " + c.Params("id"))
})

Query-params

In addition to including parameters directly in the query path, variables are substituted in the query parameters. You've probably seen URLs like http://localhost:3000/book_cover?size=140x200&filename=cover.png Parameters in URLs are mainly used to specify an entity ID or name, while query parameters are used to pass any information. This is a well-established practice, but nothing will prevent you from passing anything as a URL parameter.

Of course, there are length restrictions - many web services do not allow very long URLs. If you need a lot of data, you should use a POST request and pass parameters, not in the URL but in the request body. Below let's see the code for query-parameter handling. The Query function of the context is used to get the parameter from the query.

app.Get("/book_cover", func(c *fiber.Ctx) error {
    return c.SendString("Desired book cover has size " + c.Query("size") + " with filename " + c.Query("filename"))
})

We re-save the code, and after calling our URL, we get the parameters from the request in the response. It's essential to get the parameters in the handler and ensure they have the correct format and values. This step is called validation.

Request structure

In the handler, access the request structure and see the necessary information. Fiber hides the work with the request method and configures the routing without effort on our part. You can get the method directly as follows:

app.Get("/", func(c *fiber.Ctx) error {
  c.Request().Header.Method()
  // => []byte("GET")
})

As we can see, the context method Request will return all the required information.

Response structure

If there is a request object in the context, it is obvious that there is also a response object. It often needs to be accessed as well. Usage scenarios are varied: return the type of response returned, store the key for hashing, and add authorization information. This data is written to the response headers. You should not return files in headers, though, and there is a size restriction too.

Fiber contains useful methods that hide the raw response, but the response object is available if you want, as demonstrated in the following code snippet:

// GET http://localhost:3000/custom_header
app.Get("/custom_header", func(c *fiber.Ctx) error {
    c.Response().Header.Set("X-My-Header", "my-header-value")
    return c.SendString("Hello, World!")
})

If you query the URL in a browser, you get the standard response Hello, World! You can see the headers in the developer console or with a tool like cUrl or Postman.

Conclusion

Throughout today, we've explored several concepts involved in developing web applications. That said, it's worth remembering again - regardless of the programming language and framework, you'll have to work with HTTP requests.

You will choose whether to add parameters directly to the URL path or add them as query parameters. Make GET or POST requests and what to add or remove from the headers. If you want to use the Go programming language and Fiber framework, the ready-made examples in today's lesson will help.

Useful links

Go official site

Fiber official site

What is a URL?

Whole listing

All of the application code from the current article is attached below:

package main

import "github.com/gofiber/fiber/v2"

func main() {
	app := fiber.New()

	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("Hello, World!")
	})

	app.Get("/resource", func(c *fiber.Ctx) error {
		return c.SendString("Desired resource")
	})

	app.Get("/resource/:id", func(c *fiber.Ctx) error {
		return c.SendString("Desired resource = " + c.Params("id"))
	})

	app.Get("/user/:id/book/:title", func(c *fiber.Ctx) error {
		return c.SendString("Desired book is " + c.Params("title") + " from user " + c.Params("id"))
	})

	app.Get("/bookfile", func(c *fiber.Ctx) error {
		return c.SendString("Desired bookfile has size " + c.Query("size") + " with filename " + c.Query("filename"))
	})

	app.Get("/custom_header", func(c *fiber.Ctx) error {
		c.Response().Header.Set("X-My-Header", "my-header-value")
		return c.SendString("Hello, World!")
	})

	app.Listen(":3000")
}


Written by alexeysutyagin | Software developer
Published by HackerNoon on 2022/10/20