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.
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.
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.
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.
main
. Every Go package has a main
function, and our code is no exception.main
function, we create a new instance of our web server. It will serve requests and do the work.SendString
context method.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.
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.
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"))
})
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.
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.
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.
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.
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")
}