Handling user inputs, whether through URLs parameters, form submissions, or other methods, is fundamental to building dynamic and responsive web applications. In this topic, you'll understand how to effectively process user inputs using Gin to enhance your Go-based web apps.
Handling query parameters
In the context of web applications, query parameters are key-value pairs appended to the end of the URL and separated from the URL using a question mark ?. These parameters are connected by ampersands & and are used to provide information like user inputs to a web server when making requests.
For example, a URL using query parameters would be structured like this:
https://example.com/path?param1=value1¶m2=value2Here's a breakdown of the above URL key components:
Base URL:
https://example.com/pathis the base URL or endpoint of the resource you are trying to access.Question Mark (
?): The question mark denotes the beginning of the query parameters.Query Parameters:
param1=value1andparam2=value2are examples of query parameters. Each parameter is a key-value pair, and ampersands separate multiple parameters (&).
In Gin, query parameters can be easily parsed from the request URL using the Query() method of the gin.Context struct. Suppose you wanted to build a simple Gin app with a /welcome endpoint that allows the users to view a welcome message with details after providing their name and age as query parameters. For example, the target URL could be http://localhost:8080/welcome?name=John&age=25 .
Now, let's create a main.go file in your current project directory, and write the following code to create the /welcome endpoint:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
router := gin.Default()
// Define a route for handling GET requests with query parameters
router.GET("/welcome", handleWelcome)
err := router.Run(":8080")
if err != nil {
log.Fatal(err)
}
}
// Handler function for the /welcome route
func handleWelcome(c *gin.Context) {
// Extract query parameters from the URL
name := c.Query("name")
age := c.Query("age")
// Check if required parameters are provided
if name == "" || age == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing required parameters"})
return
}
// Build a welcome message based on the parameters
welcomeMessage := fmt.Sprintf("Welcome, %s! You are %s years old.", name, age)
// Return the welcome message as a JSON response
c.JSON(http.StatusOK, gin.H{"message": welcomeMessage})
}In the above code:
The handler for the
/welcomeroute extracts thenameandagequery parameters from the URL using thec.Query()methodIf either the
nameorageparameters are missing, the Gin server responds with a400 Bad Requesterror status code.Upon receiving both
nameandageparameters, the Gin server responds with the welcome message.
You can test the /welcome endpoint using curl to make a GET request via the command line. The output is as follows:
(base) ~ % curl "http://localhost:8080/welcome?name=John&age=25"
{"message":"Welcome, John! You are 25 years old."}Query parameters of other types
Beyond the key-value query parameter type, web apps can also handle more complex query parameter types like arrays and maps. These formats are helpful in cases where you need to pass more detailed information within a single request.
In cases where you need to pass multiple values for a single query parameter, you can use the array syntax; this approach involves repeating the same parameter name with different values in the URL, joined by ampersands &. For example, https://example.com/path?params=value1¶ms=value2¶ms=value3.
Now, imagine your app needs to handle welcoming multiple users in a single request; the URL would include multiple name parameters as follows: http://localhost:8080/welcomeMany?name=John&name=Clara&name=Greg
To parse the above URL query parameters in Gin, you could create a new route /welcomeMany that uses the c.QueryArray() method as follows:
router.GET("/welcomeMany", func(c *gin.Context) {
// Query parameter example for an array
paramValues := c.QueryArray("name")
names := strings.Join(paramValues, ", ")
message := fmt.Sprintf("Welcome %s", names)
c.JSON(http.StatusOK, gin.H{"message": message})
})In the above code snippet:
The
c.QueryArray()method extracts all values with the keynamefrom the query, enabling the handling of lists under a single parameter.The extracted values get concatenated into a single string,
namesseparated by commas using thestrings.Join(paramValues, ", ")function.A final welcome
messageis crafted and returned to the client as part of the JSON response.
After making a GET request to the /welcomeMany endpoint, you will see the following output:
(base) ~ % curl "http://localhost:8080/welcomeMany?name=John&name=Clara&name=Greg"
{"message":"Welcome John, Clara, Greg"} In other cases where a more structured set of data needs to be passed via query parameters, the map syntax comes in handy; this technique allows you to submit key-value pairs under a single parameter name.
To use map syntax for passing structured data via query parameters, you format multiple key-value pairs under a single parameter name, similar to defining key-value pairs in a Go map. Let's say you had an order parameter specifying two key-value pairs, such as item equaling bread and price equaling 10. You would structure these in the URL as order[item]=bread and order[price]=10. Consequently, when concatenated, the URL would look like this: http://localhost:8080/user?order[item]=bread&order[price]=10.
To better understand map syntax, let's create an endpoint /userWithAge designed to capture the age and name of users through query parameters, formatting the output in a JSON-like structure. The target URL for this operation would be:http://localhost:8080/user?params[name]=John¶ms[age]=25, and the below code shows the implementation:
router.GET("/userWithAge", func(c *gin.Context) {
// Extracting user information from map-based query parameters
userMap := c.QueryMap("params")
c.JSON(http.StatusOK, gin.H{"user": userMap})
})In the above code snippet:
The
c.QueryMap("params")method extracts key-value pairs from the query intouserMap, facilitating structured data retrieval.The
userMapdata is then formatted into a JSON string and returned to the client.
Finally, making a GET request to the /userWithAge endpoint would give you the following output:
(base) ~ % curl -g "http://localhost:8080/user?params[name]=John¶ms[age]=25"
{"user":{"age":"25","name":"John"}} These are just some essential examples of structured GET requests. If you want to learn more about other use cases, you can check Gin’s official documentation.
POST Request and HTML Form
HTML forms are essential tools for web applications; they enable users to input data on a web page and then send this data to a web server that can further process it. Here's an example of how an HTML form uses the POST method to submit a username and password as input:
<!DOCTYPE html>
<html lang="en">
<head>
<title>POST Form Example</title>
</head>
<body>
<h1>POST Form Example</h1>
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br>
<input type="submit" value="Submit">
</form>
</body>
</html>In the above HTML code:
The
<form>element has anactionattribute set to"/login", indicating where the form data should be sent.The
methodattribute is set to"post"indicating that the form data should be sent using the HTTP POST method.Two input fields (
usernameandpassword) are included, and a submit button allows the user to submit the form.
When a user submits the form, the browser will send an HTTP POST request to the specified action URL ("/login" in this case) with the URL encoded form data.
URL encoding is a mechanism for translating characters that are not allowed in a URL into a format that can be transmitted over the internet. URLs can only contain a limited set of characters, including letters, digits, and a few special characters, such as "-", "_", ".", and "~". Other characters, like spaces or non-alphanumeric characters, must be encoded using a special notation.
To render the HTML form as a webpage and integrate it with the backend Gin server, you'll need to create HTML files for webpage rendering and handling incoming form requests. It is a common practice to have a folder (usually named templates) to store all HTML files.
So, the next step is to create a new directory named templates and save the above HTML form code within the templates/form.html directory of your current project, and then let's modify the code within main.go to set up a Gin server that loads the form and processes its parameters on submission:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
router := gin.Default()
// Serve the HTML form at the root endpoint (GET Request)
router.LoadHTMLGlob("templates/*")
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "form.html", nil)
})
// Handle form submissions at the "/login" endpoint (POST Request)
router.POST("/login", func(c *gin.Context) {
// Retrieve form data from the request
username := c.PostForm("username")
password := c.PostForm("password")
// You can now use 'username' and 'password' data as needed
msg := fmt.Sprintf("Form submitted successfully, "+
"username = %s and password = %s", username, password)
// Respond with a confirmation message
c.String(http.StatusOK, msg)
})
err := router.Run(":8080")
if err != nil {
log.Fatal(err)
}
}The above code:
Sets up a
GETrequest handler to display the HTML form at the root URL (/).Uses the
router.LoadHTMLGlob()function to load all HTML files from thetemplatesdirectory.Then, it uses the
c.HTML()method to serve the specified HTML template by its filename:form.html.
Creates a
POSTrequest handler at the/loginURL to manage form submissions.Applies the
c.PostForm()method to extract theusernameandpasswordfields from thePOSTrequest's URL-encoded form data.Uses the
c.String()method to send a formatted string response directly to the client.
After running the above code, you can use the following curl command to simulate submitting the form with user credentials:
(base) ~ % curl -v -F username=Abby -F password=secret http://localhost:8080/login
* Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> POST /submit HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.4.0
> Accept: */*
> Content-Length: 268
> Content-Type: multipart/form-data; boundary=------------------------cQHDoTUPMWUXR7gKvXqBXr
>
<
Form submitted successfully, username = Abby and password = secretBinding form data to structs
In the previous example, you learned how users can post data to a Gin server, focusing on manual parameter extraction for form submissions. However, when you have forms that contain numerous fields, it's more convenient to use a method that binds incoming form data directly to a struct.
Gin provides the ShouldBind() method that allows you to parse and map form data to a struct automatically; this method significantly streamlines the process of handling form submissions by eliminating the need for manual data extraction. Instead, you define a struct that mirrors the form's fields, and ShouldBind() takes care of the rest, ensuring that each form input is appropriately assigned to the corresponding struct field:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
)
type LoginForm struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "form.html", nil)
})
router.POST("/login", func(c *gin.Context) {
// Bind HTML form data to the `LoginForm` struct
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
log.Printf("error parsing form data: %v", err)
c.String(http.StatusBadRequest, "Failed to process login request. "+
"Please ensure all fields are correctly filled out.")
return
}
// You can now use the struct fields with the 'username' and 'password' data:
msg := fmt.Sprintf("Form submitted successfully, "+
"username = %s and password = %s", loginForm.Username, loginForm.Password)
c.String(http.StatusOK, msg)
})
err := router.Run(":8080")
if err != nil {
log.Fatal(err)
}
}In the above example, c.ShouldBind() is used to bind the incoming form data to the LoginForm struct directly; this method efficiently handles parsing, regardless of the content type, and assigns the data to the struct fields, enabling easy access to the form data via the loginForm struct.
Finally, if you use curl to simulate submitting the form, you'll see that the output is the same as before:
(base) ~ % curl -v -F username=Abby -F password=secret http://localhost:8080/login
* Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> POST /submit HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.4.0
> Accept: */*
> Content-Length: 268
> Content-Type: multipart/form-data; boundary=------------------------cQHDoTUPMWUXR7gKvXqBXr
>
<
Form submitted successfully, username = Abby and password = secretConclusion
In this topic, you explored handling query parameters in GET requests and processing form data in POST requests using Gin. You also learned how to integrate an HTML form into a Gin application and how to bind form fields to a Go struct, streamlining the capture and validation of user input.
Remember that handling query parameters and form data is fundamental to web development. By leveraging Gin features, you can efficiently process and validate user input and are now equipped to develop more interactive and reliable web applications.