Sign In
Sign In

Affordable Managed
Database Hosting

Set up a managed cloud database in minutes, with free backups
included.
Contact Sales
Managed Cloud Database
Secure Data Encryption
Lock down your data with cutting-edge encryption both in transit and at rest. Our security protocols are constantly updated and patched by industry experts, so you can rest easy knowing your data is in safe hands.
Effortless Scaling
Your business is unique, and your database should be too. Choose from numerous environments like MySQL, PostgreSQL, Redis, and beyond, all customized for your project’s needs.
24/7 Support
With our 24/7 monitoring, we ensure your database is always up, performing optimally, and supported by specialists who are ready whenever you need them.
Cost-Effective Management
Say goodbye to budget blowouts. Our managed databases come with transparent, predictable pricing, eliminating the need for costly in-house management. Invest in your growth, not unexpected expenses.

Optimized managed database solutions

Secure, scalable, and always online.

MySQL

Streamline app development with our fully managed MySQL environments, designed for optimal performance and scalability.

PostgreSQL

Unlock the power of PostgreSQL. We manage the details: you harness its advanced capabilities for your data-driven solutions.

Redis

Accelerate with managed Redis. Blazing-fast data handling, zero management overhead — all in your control.

MongoDB

Flexible, dynamic MongoDB management lets you focus on innovation while we handle the data agility your app needs.

OpenSearch

Managed OpenSearch powers your insights. We handle the complexity, you enjoy lightning-fast, scalable search capabilities.

ClickHouse

Instant analytics with managed ClickHouse. Fast, reliable, and maintenance-free — query at the speed of thought.

Kafka

Effortless data streaming with Kafka. Our management means reliable, scalable, real-time processing for your applications.

RabbitMQ

Seamless messaging with RabbitMQ. Let us manage the queues while you build responsive, interconnected app features.

Simple and predictable pricing

MySQL
New York
1 x 3 GHz CPU
CPU
1 x 3 GHz
1 GB RAM
RAM
1 GB
20 GB NVMe
NVMe
20 GB
200 Mbps Bandwidth
Bandwidth
200 Mbps
$4
 /mo
2 x 3 GHz CPU
CPU
2 x 3 GHz
2 GB RAM
RAM
2 GB
60 GB NVMe
NVMe
60 GB
200 Mbps Bandwidth
Bandwidth
200 Mbps
$9
 /mo
2 x 3 GHz CPU
CPU
2 x 3 GHz
4 GB RAM
RAM
4 GB
80 GB NVMe
NVMe
80 GB
200 Mbps Bandwidth
Bandwidth
200 Mbps
$18
 /mo
4 x 3 GHz CPU
CPU
4 x 3 GHz
8 GB RAM
RAM
8 GB
160 GB NVMe
NVMe
160 GB
200 Mbps Bandwidth
Bandwidth
200 Mbps
$36
 /mo
6 x 3 GHz CPU
CPU
6 x 3 GHz
16 GB RAM
RAM
16 GB
320 GB NVMe
NVMe
320 GB
200 Mbps Bandwidth
Bandwidth
200 Mbps
$72
 /mo
8 x 3 GHz CPU
CPU
8 x 3 GHz
32 GB RAM
RAM
32 GB
640 GB NVMe
NVMe
640 GB
200 Mbps Bandwidth
Bandwidth
200 Mbps
$114
 /mo
16 x 3 GHz CPU
CPU
16 x 3 GHz
64 GB RAM
RAM
64 GB
1280 GB NVMe
NVMe
1280 GB
200 Mbps Bandwidth
Bandwidth
200 Mbps
$288
 /mo

Trusted by 500+ companies and developers worldwide

One panel to rule them all

Easily control your database, pricing plan, and additional services
through the intuitive Hostman management console.
Easy set up and management
Ready-to-deploy cloud database solutions come pre-configured. Choose your setup, launch your database, and begin managing your data with ease.
Saves time and resources
Forget about configuring hardware and software or manual database management—our service has it all covered for you.
Security
Deploy databases on an isolated network to maintain private access solely through your own infrastructure.
Hostman Cloud

Code locally, launch worldwide

Our servers, certified with ISO/IEC 27001, are located in Tier 3 data
centers across the US, Europe, and Asia.
🇺🇸 San Francisco
🇺🇸 San Jose
🇺🇸 Texas
🇺🇸 New York
🇳🇱 Amsterdam
🇳🇬 Lagos
🇩🇪 Frankfurt
🇵🇱 Gdansk
🇦🇪 Dubai
🇸🇬 Singapore

Compare Hostman Cloud Database
with leading providers

Managed Databases
Hostman
DigitalOcean
Google Cloud
AWS
Vultr
MongoDB
Kafka
MySQL
PostgreSQL
OpenSearch
ClickHouse
Redis
RabbitMQ
Anup k.
Associate Cloud Engineer
5.0 out of 5

"Hostman Comprehensive Review of Simplicity and Potential"

It been few years that I have been working on Cloud and most of the cloud service...
Mansur H.
Security Researcher
5.0 out of 5

"A perfect fit for everything cloud services!"

Hostman's seemless integration, user-friendly interface and its robust features (backups, etc) makes it much easier...
Adedeji E.
DevOps Engineer
5.0 out of 5

"Superb User Experience"

For me, Hostman is exceptional because of it's flexibility and user-friendliness. The platform's ability to offer dedicated computing resources acr...
Yudhistira H.
Mid-Market(51-1000 emp.)
5.0 out of 5

"Streamlined Cloud Excellence!"

What I like best about Hostman is their exceptional speed of deployment, scalability, and robust security features. Their...
Mohammad Waqas S.
Biotechnologist and programmer
5.0 out of 5

"Seamless and easy to use Hosting Solution for Web Applications"

From the moment I signed up, the process has been seamless and straightforward...
Mohana R.
Senior Software Engineer
5.0 out of 5

"Availing Different DB Engine Services Provided by Hostman is Convenient for my Organization usecases"

Hostman manages the cloud operations...
Faizan A.
5.0 out of 5

"Hostman is a great fit for me"

Hostman is a great fit for me. What do you like best about Hostman? It was very easy to deploy my application and create database, I didn't have
Adam M.
5.0 out of 5

"Perfect website"

This website is extremely user friendly and easy to use. I had no problems so didn't have to contact customer support. Really good website and would recommend to others.
Anup K.
4.0 out of 5

"Simplifying Cloud Deployment with Strengths and Areas for Growth"

What I like best about Hostman is its unwavering commitment to simplicity...
Naila J.
5.0 out of 5

"Streamlined Deployment with Room for Improvement"

Hostman impresses with its user-friendly interface and seamless deployment process, simplifying web application hosting...

More cloud services from Hostman

See all Products

Latest News

PostgreSQL

Logical Replication in PostgreSQL

When deploying applications, having more than one copy of the database is always beneficial. After creating copies, it is essential to ensure they are all synchronized. The process of synchronizing database copies is called replication. Logical replication in PostgreSQL refers to the synchronization of copies without being tied to a specific physical data representation on a disk. It is independent of processor architecture, platform, or database system version. Synchronization is performed based on a replication identifier, which is typically the primary key. Logical replication uses a publish-and-subscribe model. Replication Process In general, the replication process consists of the following steps: Creating one or more publications on the publisher node. Subscribing one or more subscribers to one or more publications. Copying a snapshot of the publisher's database to the subscriber. This step is also known as the table synchronization phase. It is possible to create multiple table synchronization workers to reduce the time required for this phase. However, there can only be one synchronization process for each table. Sending the subsequent changes made on the publisher node to the subscriber node. These changes are applied in the commit order to ensure transactional consistency. The subscriber node fetches changes as they occur in the publisher's database in real-time, ensuring that the subscriber and publisher databases remain synchronized. This mechanism ensures up-to-date data consistency across the replicated databases. Logical Replication in Practice Suppose you want to set up logical replication on a single host. To achieve this, use different ports—for example, the publisher will operate on port 5432, and the subscriber on port 5431. Edit the Configuration File Start by editing the PostgreSQL configuration file: sudo nano /etc/postgresql/10/main/postgresql.conf Uncomment the wal_level parameter and set it to logical. It should look like this: wal_level = logical Save and close the configuration file, then restart PostgreSQL: sudo systemctl restart postgresql Export Global Objects On the master, execute the following command for the main database: pg_dumpall --database=postgres --host=192.168.1.2 --no-password --globals-only --no-privileges | psql The pg_dumpall command exports databases in script format. The --database parameter specifies the database used for connecting and exporting global objects and locating other databases. By default, it uses the postgres database. The --globals-only parameter ensures only global objects are exported, excluding the database contents. For detailed information, consult the PostgreSQL documentation. Export Schema on the Replica On the replica, run: pg_dump --dbname=db_name --host=192.168.1.2 --no-password --create --schema-only | psql Prepare Data for Testing Create a test table with two columns: CREATE TABLE table1(x int primary key, y int); The x column will store the primary key. The y column will store integer values. Insert a sample row: INSERT INTO table1 VALUES(10, 11); At this point, the table contains a single row where the primary key is 10 and the value is 11. This minimal dataset is enough to verify synchronization. Create a Publication on the Master Create a publication that replicates the desired table: CREATE PUBLICATION my_publication FOR TABLE table1; The FOR TABLE parameter allows you to specify which tables to replicate. You can limit the changes to be published or include additional tables later. To create a publication for all existing and future tables, use the ALL TABLES parameter. For more details, refer to the PostgreSQL documentation. The publication named my_publication is ready. Now it’s time to create a subscription on port 5431. Recreate the Table on the Subscriber Node On the subscriber, create the same table structure as on the publisher: CREATE TABLE table1(x int primary key, y int); Create a Subscription on the Replica Create a subscription named my_subscription: CREATE SUBSCRIPTION my_subscription CONNECTION 'host=localhost port=5432 dbname=postgres' PUBLICATION my_publication; Verify Synchronization Query the table on the subscriber: SELECT * FROM table1; This command will display the rows synchronized from the publisher. Initially, it should return the row added earlier (11 with the primary key 10). How It Works The CREATE SUBSCRIPTION command creates a subscription for the current database, which begins receiving logical changes from the publication my_publication. Upon execution, a logical replication worker is created to fetch changes from the publisher. On the publisher side, a walsender process starts to read the WAL (Write-Ahead Log), decode changes, and send them to the subscriber. To test the synchronization, add additional rows on the publisher: INSERT INTO table1 VALUES(20, 21), (30, 31); Verify that the subscriber displays these rows: SELECT * FROM table1; If you have multiple servers, additional configuration is required. Allow Connections on the Publisher On the main server, edit the configuration file to listen on the private IP address: sudo nano /etc/postgresql/10/main/postgresql.conf Locate the listen_addresses parameter and modify it to include the private IP address of the master: listen_addresses = 'localhost, MASTER_PRIVATE_IP' Configure Access Control Edit the pg_hba.conf file on the publisher to allow incoming connections from the replica: sudo nano /etc/postgresql/10/main/pg_hba.conf Add the following line, replacing REPLICA_PRIVATE_IP with the actual private IP address of the replica: host replication postgres REPLICA_PRIVATE_IP/32 md5 Look for the comment: # If you want to allow non-local connections, you need to add more. Add your new rule below this line. Firewall Configuration On the publisher, allow traffic from the replica to port 5432: sudo ufw allow from REPLICA_PRIVATE_IP to any port 5432 Apply Changes Restart PostgreSQL to apply all changes: sudo systemctl restart postgresql Troubleshooting Issues If replication doesn’t seem to work, check the PostgreSQL logs on the replica for possible errors. The log file is typically located at: /var/log/postgresql/postgresql-10-main.log. Common Issues and Solutions: Private Network Not Enabled. Ensure both servers are in the same private network or correctly configured for cross-network access. Incorrect IP Address Configuration. Verify that the server is listening on the correct private network IP address. wal_level Not Set to logical. Double-check the wal_level parameter in the PostgreSQL configuration. Firewall Blocking Connections. Confirm that the firewall is not blocking incoming connections on the required port (e.g., 5432). Mismatch in Table or Field Names. Ensure that table and column names match the publisher and subscriber exactly. Table Not Included in the Publication. Verify that the table is added to the publication on the publisher. After addressing these issues, replication should resume automatically. If not, drop the existing subscription and recreate it: DROP SUBSCRIPTION my_subscription; Physical Replication Overview PostgreSQL supports two types of replication: logical (discussed above) and physical replication. Here's a brief overview of physical replication. Key Features of Physical Replication: Introduced in PostgreSQL 9.0. Physical replication synchronizes databases at the file level. Block-Level Synchronization. Changes are tracked using precise block addresses and replicated byte-by-byte. Write-Ahead Log (WAL). Changes from the master are transmitted via WAL and applied on the standby server. Limitations of Physical Replication: No Partial Database Replication: You cannot replicate only a portion of the database. High Overhead: All changes are transmitted, potentially increasing network load. Platform Restrictions: Physical replication requires identical server platforms, including CPU architecture (e.g., Windows to Windows or Linux to Linux). Version Compatibility: Databases on different PostgreSQL versions cannot synchronize. Conclusion This guide covered setting up and managing logical replication in PostgreSQL, including troubleshooting common issues. We also briefly touched on physical replication, highlighting its characteristics and limitations. For simplified database management, consider cloud database services like Hostman, which offers managed PostgreSQL and other database solutions to streamline deployment and scaling.
11 December 2024 · 7 min to read
Go

For Loops in the Go Programming Language

A loop is a block of code that runs until a specified condition is met or a required number of repetitions is completed. Loops are convenient for solving tasks where a program needs to repeat the same actions multiple times. For example, imagine you have a list of directors. You need to extract each director's last name and display it on the screen. Instead of manually accessing each element of the list, it's easier to use a loop. A loop will iterate through the list and display each last name on the screen. Loops in Go In Go, there are only for loops. There are no while or do while loops like in some other languages. Similar concepts are implemented using the same for loop. This design choice makes the code more readable. Developers don't have to decide on a looping strategy — if you need to repeat actions, there's for, which can be used in various ways. Let's explore how to create loops in Golang to solve specific tasks. ForClause The structure of a ForClause is simple. It consists of a condition and a body. The code inside the body executes if the condition is evaluated as true. for i := 0; i < 6; i++ { fmt.Println(i) } Here: i := 0 is the initializer. It sets the starting value of the loop. i < 6 is the condition. If it is evaluated as true, the code inside the loop is executed. fmt.Println(i) sequentially prints numbers from 0 to 5. i++ is the post-operation that increments i by 1 after each iteration. The code starts with i = 0. Since 0 < 6, the condition is true, and 0 is printed. Then, i++ increments i by 1, making i = 1. The loop continues as long as i < 6. When i becomes 6, the condition i < 6 is false, and the loop stops. The number 6 is not printed. Output: 0 1 2 3 4 5 You don't have to start at zero or stop at a fixed value. The for loop in Go allows you to adjust the logic as needed. for i := 100; i < 150; i = i + 10 { fmt.Println(i) } Output: 100 110 120 130 140 If you modify the condition slightly, you can include the number 150: for i := 100; i <= 150; i = i + 10 { fmt.Println(i) } Output: 100 110 120 130 140 150 You can also iterate in reverse, from the end to the beginning, by modifying the condition and the post-operation. for i := 50; i > 0; i -= 10 { fmt.Println(i) } Here, the loop starts with i = 50. On each iteration, it checks if i > 0. If the condition is true, it subtracts 10 from the current value of i. Output: 50 40 30 20 10 Note that 0 is not printed because the condition requires i > 0. Loop with a Condition If you remove the initializer and post-operator from the syntax, you get a simple construct that works based on a condition. The loop declaration in this case looks like this: i := 0 for i < 6 { fmt.Println(i) i++ } If you are familiar with other programming languages, you might recognize this as similar to a while loop. In this example, i is defined outside the loop. The for loop only has a condition, which keeps the loop running while i is less than 6. Note that the increment operation (i++), previously specified as a post-operator, is now inside the body. Sometimes, the number of iterations is unknown in advance. You can't specify a condition for ending the loop in such cases. To avoid infinite loops, Go supports the break keyword. Here's a simple example: func main() { i := 0 for { fmt.Println("Hello") if i == 5 { break } i++ } } Initially, i = 0. The loop runs indefinitely, printing "Hello" each time. However, when i reaches 5, the break statement is executed, and the program stops. RangeClause Go also provides another type of loop — the RangeClause. It is similar to ForClause, but it returns two values by default: the index of an element and its value. package main import "fmt" func main() { words := []string{"host", "man", "hostman", "cloud"} for i, word := range words { fmt.Println(i, word) } } Output: 0 host 1 man 2 hostman 3 cloud To omit the index, use an underscore _ as a placeholder: package main import "fmt" func main() { words := []string{"host", "man", "hostman", "cloud"} for _, word := range words { fmt.Println(word) } } Output: host man hostman cloud You can also use range to add elements to a list: package main import "fmt" func main() { words := []string{"host", "man", "hostman", "cloud"} for range words { words = append(words, "great") } fmt.Printf("%q\n", words) } Output: ["host" "man" "hostman" "cloud" "great" "great" "great" "great"] In this example, the word "great" is added for each element in the original length of the words slice. Suppose you have a slice of 10 zeros and need to populate it with numbers from 0 to 9: package main import "fmt" func main() { integers := make([]int, 10) fmt.Println(integers) for i := range integers { integers[i] = i } fmt.Println(integers) } [0 0 0 0 0 0 0 0 0 0] [0 1 2 3 4 5 6 7 8 9] You can use range to iterate over each character in a string: package main import "fmt" func main() { hostman := "Hostman" for _, letter := range hostman { fmt.Printf("%c\n", letter) } } Output: H o s t m a n This allows you to process each character in a string individually. Nested Constructs A for loop can be created inside another construct, making it nested. We can represent its syntax as: for { [Action] for { [Action] } } First, the outer loop starts running. It executes and then triggers the inner loop. After the inner loop finishes, the program returns to the outer loop. This process repeats as long as the given condition holds or until the program encounters a break statement. There is also a risk of creating an infinite loop, which even the powerful resources of Hostman wouldn’t handle, as the program would never terminate. To avoid this, always ensure the condition is properly checked or use the break operator. Here’s a simple example to demonstrate nested loops: package main import "fmt" func main() { numList := []int{1, 2} alphaList := []string{"a", "b", "c"} for _, i := range numList { fmt.Println(i) for _, letter := range alphaList { fmt.Println(letter) } } } Output: 1 a b c 2 a b c This example clearly demonstrates the order of operations: The first value from numList (1) is printed. The inner loop executes, printing each value from alphaList (a, b, c). The program returns to the outer loop and prints the next value from numList (2). The inner loop runs again, printing the values of alphaList (a, b, c) a second time. Conclusion Using for loops in Go is straightforward. Depending on the task, you can choose one of the three main forms of for or combine them to create nested constructs. You can control the loop's behavior by modifying the condition, initializer, and post-operator or by using break and continue statements. Nested loops provide flexibility and power but should be used carefully to avoid infinite loops or excessive computational overhead. You can deploy Go applications (such as Beego and Gin) on our app platform.
11 December 2024 · 6 min to read
Go

How to Install Go on Windows

Go, or Golang, is a high-performance, multithreaded programming language developed by Google in 2007 and released in 2009. To this day, Golang continues to gain popularity.  The Go programming language supports many operating systems, making it a versatile choice for development across various platforms. In this guide, we will walk through the step-by-step process of installing Golang on Windows. Installing Go on Windows Go supports Windows 7 and newer versions. Ensure that you have a supported version of the OS installed. In this guide, we will use Windows 11. You will also need an administrator account to configure environment variables. To install Golang on Windows: Download the installer for the latest version of Microsoft Windows from the official Go website. If needed, you can select any other available version of the language instead of the latest one. Once the file has finished downloading, run it and follow the installation wizard's instructions. If necessary, you can change the file location. This will be useful when configuring environment variables. After the installation, check if Golang was successfully installed on your system. To do this, open the terminal (Win + R → cmd) and run the following command: go version The output should show the version of Go you just installed. For example: To update Golang to a newer version on Windows, you must uninstall the old version and follow the instructions to install the new one. Now, let's move on to setting up environment variables so that Go works properly. Setting Up Environment Variables Setting up environment variables is an important step in installing Go on Windows, as it allows the operating system to determine where the necessary Go files and directories are located. For Go to work correctly, two environment variables are required: GOPATH points to where Go stores downloaded and compiled packages. PATH allows the system to find Go executable files without specifying their full paths. GOPATH First, let's set up the GOPATH environment variable. For this, you need to organize a workspace where Go files and projects will be stored. In this guide, we will create a workspace at C:\GoProject. We will also add two directories to this folder: bin – for storing executable files (binary files). Go creates an executable file and places it in this directory when you compile your project. src – for storing Go source files. All .go files will be placed here. After creating the workspace, we will set the GOPATH environment variable. To do this, go to the Control Panel → System and Security → System and click on Advanced System Settings. There is also an easier way to access system properties: open the Run window (Win + R) and enter: sysdm.cpl Click on Environment Variables, then click the New button under the User Variables section. Here, you need to fill in two fields: the variable name and its value. In the Variable name field, enter GOPATH, and in the Variable value field, enter the path to the workspace you created earlier (in our case, C:\GoProject). Click OK twice to save the changes. To verify the creation of the system variable, open the Run window (Win + R) and enter the string: %GOPATH% If everything was done correctly, your workspace will open. PATH The PATH environment variable should have been automatically added after we installed Go. To check this, go to the Control Panel → System and Security → System and click on Advanced System Settings. In the window that opens, you need to find PATH among the system variables. To view its values, double-click on it. In the new window, there should be an entry that holds the path to the Go bin folder. In our case, it is C:\Program Files\Go\bin. If your value does not match what was specified during the Go installation, change it to the correct one using the Edit button. At this point, the installation of Golang on Windows and the setup of environment variables is complete. Now we can check its functionality by writing and running our first program. Verifying Installation To check the functionality of the newly installed Golang on Windows: Сreate a test file with the .go extension in the workspace (C:\GoProject\src). For example, ExampleProgram.go. Add the following simple code: package mainimport "fmt"func main() {    fmt.Println("Hello, Go has been successfully installed into your system!")} The program should display a message confirming that Go has been successfully installed on your system. To compile and run the program, enter the following command in the terminal: go run %GOPATH%/src/ExampleProgram.go As shown in the image below, the program compiles and runs, displaying the specified text on the screen. Conclusion Installing Go on Windows is a straightforward process, involving downloading the installer, setting up environment variables, and verifying the installation. Once Go is properly configured, you can easily start developing applications. With support for multiple operating systems, Go remains a powerful and versatile language, ideal for cross-platform development. On our app platform you can deploy Golang apps, such as Beego and Gin. 
10 December 2024 · 5 min to read
Go

Type Conversion in Go

Go is a statically typed programming language, meaning that data types are tied to variables. If you declare a variable as int to store numerical values, you cannot store a string in it. This rule works in the reverse direction as well. Static typing protects developers from errors where the program expects one data type and gets another. However, this strict binding can be limiting when performing certain operations. Go provides type conversion (or type casting) to overcome this limitation. This formal process allows developers to convert integer values to floating-point numbers, strings to numbers, and vice versa. This article will help you understand how to perform such conversions. Data Types in Go The basic types in Go are as follows: bool — Boolean values: true or false string — Strings int, int8, int16, int32, int64 — Signed integer types uint, uint8, uint16, uint32, uint64, uintptr — Unsigned integer types byte — Alias for uint8 rune — Alias for int32 float32, float64 — Floating-point numbers complex64, complex128 — Complex numbers The types int, uint, and uintptr have a width of 32 bits in 32-bit systems and 64 bits in 64-bit systems. When you need an integer value, you should use int unless you have a specific reason for using a sized or unsigned integer type. Go does not have a char data type. The language uses byte and rune to represent character values. byte represents ASCII characters, while rune represents a broader set of Unicode characters encoded in UTF-8. To define characters in Go, you enclose them in single quotes like this: 'a'. The default type for character values is rune. If you do not explicitly declare the type when assigning a character value, Go will infer the type as rune: var firstLetter = 'A' // Type inferred as `rune` You can explicitly declare a byte variable like this: var lastLetter byte = 'Z' Both byte and rune are integer types. For example, a byte with the value 'a' is converted to the integer 97. Similarly, a rune with the Unicode value '♥' is converted to the corresponding Unicode code point U+2665, where U+ indicates Unicode, and the numbers are in hexadecimal, which is essentially an integer. Here's an example: package main import "fmt" func main() { var myByte byte = 'a' var myRune rune = '♥' fmt.Printf("%c = %d and %c = %U\n", myByte, myByte, myRune, myRune) } Output: a = 97 and ♥ = U+2665 When you need to convert from int to string or vice versa, you essentially take the type initially assigned to a variable and convert it to another type. As mentioned earlier, Go strictly formalizes these actions. The examples in this article will help you understand the basics of such conversions. Number Conversion in Go Converting numeric types can be useful when solving various tasks. For example, we decided to add a calculator to the website. It should perform only one operation: division. The main requirement is that the result be accurate down to the last digit. However, when dividing two integer variables, the result may be inaccurate. For example: package main import "fmt" func main() { var first int = 15 var second int = 6 var result = first / second fmt.Println(result) } Output: 2 After executing this code, you get 2. The program outputs the nearest integer quotient, but this is far from the precise division you need. Such a calculator is not useful. To improve the accuracy, you need to cast both variables to float. Here's how you can do it: package main import "fmt" func main() { var first int = 15 var second int = 6 var result = float64(first) / float64(second) fmt.Println(result) } Output: 2.5 Now the output will be precise — 2.5. It was quite easy to achieve by simply wrapping the variables with the float64() or float32() conversion functions. Now the calculator works as expected. Product metrics are not a concern, as the feature is technically implemented correctly. You can also divide numbers without explicitly converting them to float. When you use floating-point numbers, other types are automatically cast to float. Try this code: package main import "fmt" func main() { a := 5.0 / 2 fmt.Println(a) } Output: 2.5 Even though you didn’t explicitly use the float64() or float32() wrapper in the code, Go's compiler automatically recognizes that 5.0 is a floating-point number and performs the division with the floating-point precision. The result is displayed as a floating-point number. In the first example with division, you explicitly cast the integers to float using the float64() function. Here’s another example of converting from int64 to float64: package main import "fmt" func main() { var x int64 = 57 var y float64 = float64(x) fmt.Printf("%.2f\n", y) } Output: 57.00 The two zeros after the decimal point appear because we added the %.2f\n format specifier. Instead of 2, you could specify any other number, depending on how many decimal places you want to display. You can also convert from float to int. Here's an example: package main import "fmt" func main() { var f float64 = 409.8 var i int = int(f) fmt.Printf("f = %.3f\n", f) fmt.Printf("i = %d\n", i) } Output: f = 409.800i = 409 In this example, the program prints f = 409.800 with three decimal places. In the second print statement, the float is first converted to int, and the decimal part is discarded. Note that Go does not perform rounding, so the result is 409 without any rounding to the nearest integer. Strings Conversion in Go In Golang, we can convert a number to a string using the method strconv.Itoa. This method is part of the strconv package in the language's standard library. Run this code: package main import ( "fmt" "strconv" ) func main() { a := strconv.Itoa(12) fmt.Printf("%q\n", a) } The result should be the string "12". The quotes in the output indicate that this is no longer a number. In practice, such string-to-number and number-to-string conversions are often used to display useful information to users. For example, if you're building an online store, you can host it at Hostman, implement the core business logic, and fill it with products. After some time, the product manager suggests improving the user profile. The user should see the amount they have spent and how much more they need to spend to reach the next level. To do this, you need to display a message in the user profile that consists of a simple text and a set of digits. Try running this code: package main import ( "fmt" ) func main() { user := "Alex" sum := 50 fmt.Println("Congratulations, " + user + "! You have already spent " + lines + " dollars.") } The result will be an error message. You cannot concatenate a string and a number. The solution to this problem is to convert the data in Go. Let's fix the code by converting the variable lines to a string: package main import ( "fmt" "strconv" ) func main() { user := "Alex" sum := 50 fmt.Println("Congratulations, " + user + "! You have already spent " + strconv.Itoa(sum) + " dollars.") } Now, there will be no error, and the output will display the correct message with the proper set of digits. Of course, this is a simplified example. In real projects, the logic is much more complex and challenging. However, knowing the basic operations helps avoid a large number of errors. This is especially important when working with complex systems. Let's go back to our example. The product manager comes again and says that customers want to see the exact total amount of their purchases in their profile, down to the pennies. An integer value won't work here. As you already understood from the examples above, all digits after the decimal point are simply discarded. To make sure the total purchase amount in the user profile is displayed correctly, we will convert not an int, but a float to a string. For this task, there is a method fmt.Sprint, which is part of the fmt package. package main import ( "fmt" ) func main() { fmt.Println(fmt.Sprint(421.034)) f := 5524.53 fmt.Println(fmt.Sprint(f)) } To verify that the conversion was successful, concatenate the total with the string. For example: package main import ( "fmt" ) func main() { f := 5524.53 fmt.Println("Alex spent " + fmt.Sprint(f) + " dollars.") } There is no error now, and the information message correctly displays the floating-point number. Customers can see how much money they've spent in your store, with all expenses accounted for down to the penny. A common reverse task is to convert a string into numbers. For example, you have a form where the user enters their age or any other numeric values. The entered data is saved in the string format. Let's try working with this data— for instance, performing a subtraction: package main import ( "fmt" ) func main() { lines_yesterday := "50" lines_today := "108" lines_more := lines_today - lines_yesterday fmt.Println(lines_more) } The result of running this code will be an error message, as subtraction cannot be applied to string values. To perform mathematical operations on data stored as strings, you need to convert them to int or float. The choice of method depends on the type you will convert the string to. If you are working with integers, use the strconv.Atoi method. For floating-point numbers, use the strconv.ParseFloat method. package main import ( "fmt" "log" "strconv" ) func main() { lines_yesterday := "50" lines_today := "108" yesterday, err := strconv.Atoi(lines_yesterday) if err != nil { log.Fatal(err) } today, err := strconv.Atoi(lines_today) if err != nil { log.Fatal(err) } lines_more := today - yesterday fmt.Println(lines_more) } In this example, you use the if operator to check whether the conversion was successful. If an error occurs, the program will terminate, and the error information will be saved in the log. If the conversion is successful, the output will give you the correct result: 108 - 50 = 58. If you try to convert a string that does not contain a numerical value in the same way, you will receive an error message: strconv.Atoi: parsing "not a number": invalid syntax Try running this code: package main import ( "fmt" "strconv" ) func main() { a := "not a number" b, err := strconv.Atoi(a) fmt.Println(b) fmt.Println(err) } The code from the example above will fail because you are trying to convert a string whose value is not a number into a numeric type. Strings can also be converted to byte slices and back using the []byte() and string() constructs.  package main import ( "fmt" ) func main() { a := "hostman" b := []byte(a) c := string(b) fmt.Println(a) fmt.Println(b) fmt.Println(c) } In this function, you save the string to variable a, then convert the same string into a byte slice and save it to variable b, then turn the byte slice into a string and save the result to variable c. The output will be like this: hostman[104 111 115 116 109 97 110]hostman This simple example shows that you can easily convert strings to byte slices and back. Conclusion In this article, we only covered the basics. We looked at the available data types and how to perform type conversion in Go. If you want to learn more, explore the language documentation or at least the "A Tour of Go" tutorial — it's an interactive introduction to Go divided into three sections. The first section covers basic syntax and data structures, the second discusses methods and interfaces, and the third introduces Go's concurrency primitives. Each section concludes with several exercises so you can practice what you've learned.  In addition,  you can deploy Go applications (such as Beego and Gin) on our app platform.
10 December 2024 · 10 min to read
PHP

How to Install PHP and PHP-FPM on Ubuntu 24.04

In this guide, we will describe installing PHP and PHP-FPM on Ubuntu 24.04. PHP, which stands for Hypertext Preprocessor, is a language that is widely used and open-sourced, mainly for web development. PHP is the only PHP FastCGI implementation, that is extremely useful for high-traffic websites. At the end of this guide, you should be ready to go with PHP running on your server. Prerequisites Before we start, please confirm you have the following: Ubuntu 24.04 LTS installed on the server A user account with the sudo access An essential command-line operation understanding A reliable internet connection for downloading software packages To ensure that your system is up to date, run the following commands: sudo apt updatesudo apt upgrade Install Apache Launch the Apache web server using the following command: sudo apt install apache2 Install PHP Let's begin with installing the PHP package in Ubuntu 24.04 server. First, open a terminal on your Ubuntu system. PHP and common modules are included in the installation action: sudo apt install php That command installs the core PHP package, the command-line interface, and common libraries. Make sure the installation works: php -v Install PHP Extensions PHP extensions are the way to go to extending PHP installation with certain functions. Start by installing extensions: sudo apt install php-curl php-mbstring php-xml Short description: php-mysql: Allows MySQL database connection php-gd: Adds ability to manipulate images php-curl: Makes possible to communicate with servers php-mbstring: Provides multibyte string support php-xml: Enables XML support php-zip: Enables ZIP support Additional extensions can be installed as you see fit for your projects. You can search them using: apt-cache search php- Install and Configure PHP-FPM PHP-FPM is essential when dealing with high-traffic websites. To install and configure it: Install the package: sudo apt install php-fpm Launch PHP-FPM service. Depending on the installation, version number may differ. sudo systemctl start php8.3-fpm Tell PHP-FPM to go on boot: sudo systemctl enable php8.3-fpm Verfy PHP-FPM is working: systemctl status php8.3-fpm This will output a response that says "Active (Running)" if everything is working as expected. Test PHP and PHP-FPM To ensure that PHP and PHP-FPM are both running with no problems, create a test file then serve it via the website's server. Let's say it uses Apache in this example: Generate PHP Info File. To show PHP settings using the phpinfo() function, do the following: mkdir -p /var/www/htmlecho "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php Set Up Apache for PHP-FPM. Ensure Apache is made compatible for PHP-FPM, by first finding Apache configuration file (usually /etc/apache2/sites-available/000-default.conf) then inserting: <FilesMatch \.php$>   SetHandler "proxy:unix:/var/run/php/php8.3-fpm.sock|fcgi://localhost/"</FilesMatch> Remember we must alter specific PHP version and socket path to suit individual settings of the server. Activate PHP and PHP-FPM. Enable PHP and PHP-FPM following these instructions: sudo apt install libapache2-mod-phpsudo a2enmod proxy_fcgi setenvif Reboot Apache. Apply changes by restarting Apache server: sudo systemctl restart apache2 Access PHP Info Page. First open your web browser and go to: http://your_server_ip/info.php Replace [server_ip] with the server IP address or domain. You can see details of your PHP installation. Install Multiple PHP Versions For particular projects you might need to run different applications, each one may require different functionalities. This is the way to manage and manipulate multiple PHP versions on Ubuntu 24.04. First, add PHP repository: sudo apt install software-properties-commonsudo add-apt-repository ppa:ondrej/php && sudo apt update Install PHP versions you need: sudo apt install php8.1 php8.1-fpm Deselect one PHP version and elect the other: sudo update-alternatives --set php /usr/bin/php8.1 If you are using multiple PHP versions, ensure that your web server is pointing to the appropriate PHP-FPM socket. Securing PHP and PHP-FPM: Best Practices As a web developer, you know the importance of incorporating both PHP and PHP-FPM into web applications that are safe and robust. In this section, we will introduce a number of security steps that you should adapt using PHP and PHP-FPM. 1. Keep PHP and PHP-FPM Updated PHP and PHP-FPM should be up to date. Doing regular updates will eliminate known security breaches and provide overall security improvements. You need to check for updates as often as possible then update the system as soon as the updates are available. 2. Configure PHP Securely To configure PHP securely, start by disabling unnecessary and potentially dangerous functions, such as exec, shell_exec, and eval, in the PHP configuration file (php.ini). Use open_basedir directive to restrict PHP’s access to specific directories, preventing unauthorized access to sensitive files. Set display_errors to Off in production to avoid exposing error messages that could provide insights to attackers. Limit file upload sizes and execution times to reduce the risk of resource exhaustion attacks. Besides, ensure that PHP runs under a dedicated, restricted user account with minimal permissions to prevent privilege escalation. Regularly update PHP to the latest stable version to patch vulnerabilities and improve security. 3. Use Safe Error Reporting To ensure an error-free application, it is quite handy locating and correcting code bugs in a development environment. In production environment, you have the possibility to hide the PHP errors by setting the display_error directive to be off, and you should also set the log_errors directive to be On, thus this will help you prevent PHP from showing errors to the users whereas your server will log it in a safe location without problems to users. 4. Implement Input Validation Being aware of the input validations is quite crucial during the programming of your software. Make sure that all deficiencies are tested and only SQL statements containing their SQL equivalent that can produce outwardly neutral queries via prepared statements is considered safe. 5. Secure PHP-FPM Configuration PHP-FPM is required to run using a non-usual user account with minium rights. Furthermore, access to the PHP-FPM socket or port should be very limited to the web application. 6. Enable Open_basedir You need to bind open_basedir directive in order to restrict access files within the given directory. In this case, if you attempt to visit a forbidden directory and the request is accidentally transmitted to the server, PHP will prevent you from doing so. 7. Use HTTPS We need to secure web calls by making apps HTTPS-only, which is the only prominent way to block all the known hacking tricks. Conclusion With this guide, you've successfully set up PHP and PHP-FPM on Ubuntu 24.04. Your server is now configured for dynamic web applications. To maintain security and performance, remember to keep the system and packages regularly updated.
09 December 2024 · 6 min to read
Mail

How to Configure Postfix Using External SMTP Servers

Postfix is a widely used tool for routing and delivering emails. Known for its adaptability, reliability, and easy setup, it's essential to email systems. It ensures smooth message delivery and allows administrators to manage email traffic efficiently. To install Postfix, you will need to install the software, configure it with an external SMTP server, and set up verifications. Follow these guidelines for a seamless setup. Before moving to the main process, ensure you have: sudo privileges or root access on a Linux server  An external SMTP server (like Gmail)  Installing Postfix Employ the instructions below to install Postfix across several Linux distros: On Debian-based Linux Distros (like Ubuntu) sudo apt install postfix On Red Hat-based Linux Distros (like CentOS) sudo yum install postfix On Fedora sudo dnf install postfix On Arch Linux sudo pacman -S postfix During installation, users will see a setup window. This window will ask for basic setup settings. After finalizing, complete the installation. Configuring Postfix Correctly configuring Postfix is crucial for successful email delivery. This involves updating configuration files, activating authentication, and setting methods for processing and delivering mails. Here's the process: Step 1: Configuration File Modification The main.cf (Postfix configuration) contains principal settings, and to tweak them, open the file using: sudo nano /etc/postfix/main.cf Note: By default, new servers have ports 465 and 587 blocked. To unblock these ports, reach out to technical support. Step 2: Configuration with an External SMTP Server Set up the relay host and enable security protocols by adding the provided lines to the file: relayhost = [smtp.example.com]:587smtp_sasl_auth_enable = yessmtp_sasl_password_maps = hash:/etc/postfix/sasl_passwdsmtp_sasl_security_options = noanonymoussmtp_tls_security_level = encryptsmtp_tls_note_starttls_offer = yes Here: The initial line configures the Postfix relay host. This line sets the SMTP and port (587 for TLS); if you’re using Gmail, replace "smtp.example.com" with "smtp.gmail.com." The second line enables SASL authentication. The third line points to the file containing your SMTP credentials (an essential file that helps setup Postfix map sasl_password. The fourth line prevents anonymous connections. The fifth line causes the utility to utilize TLS encryption. The sixth line reports the server's STARTTLS offer. Save the file once you’ve adjusted the necessary settings. Step 3: Construct the SASL Credentials File Create a SASL password file via your SMTP credentials: sudo nano /etc/postfix/sasl_passwd Insert the credentials in the specified format within the file: [smtp.example.com]:587 [email protected]:password Substitute [smtp.example.com] with your chosen server (e.g., smtp.gmail.com). Swap out password and [email protected] with your real email address and corresponding password. Produce an app-specific password in Gmail by accessing the App Passwords segment of your account settings. Step 4: Protect the SASL Credentials Once done, protect your credentials via provided commands: sudo chmod 600 /etc/postfix/sasl_passwd sudo postmap /etc/postfix/sasl_passwd The first command restricts access to the credentials file, permitting read access solely to the root user. The application will authenticate via the hash database file generated by the second command. Step 5: Restart Postfix Restart to apply the changes: sudo systemctl restart postfix Note: If encountering an error like "fatal: the Postfix mail system is not running," double-check that the server is configured correctly and that all processes have been exactly followed. Testing the SMTP Server Now that everything has been modified, you can send mail. Before sending, install mailutils on your Linux PC using: sudo apt install mailutils Post-installation, check the configuration by sending a test mail using the specified format below: echo "Test email from Postfix" | mail -s "Test Postfix" [email protected] The first part displays the beginning part of the text intended for the mail body. Second is the pipe symbol (|) which directs the echo command’s output straight into the mail command. Third is the mail command that establishes the email’s subject when used with -s option. The last part indicates the email address of the test message's recipient. To make sure everything is functioning and that the test mail was delivered correctly, delve into the mail logs using: sudo tail -f /var/log/mail.log This log file provides a snapshot of recent activities. If the test mail logs successful, your setup is complete. Note: If experiencing difficulties receiving Gmail messages, use below guidelines: Enter your current login details to access Gmail through a web browser. Locate the gear icon at the top right, click it and select "See all settings." In the Gmail menu, pick "Forwarding and POP/IMAP." Go with "IMAP access" and activate "Enable IMAP." Continue scrolling down, and hit "Save Changes. By adhering to these guidelines, you'll activate IMAP in Gmail and improve the message delivery system. Setting Up Email Forwarding Email forwarding configuration ensures seamless redirection of incoming mails from one address to another, guaranteeing you never miss a message. This functionality is useful to centralize email management or direct system alerts to an external email address. Take the following action to configure forwarding: Step 1: Modify Aliases File Begin by adding modifications to the aliases file, which can be accessed using: sudo nano /etc/aliases To specify forwarding addresses, they must be detailed in the aliases file. For instance: root:    [email protected] This command ensures mails destined for the root are passed along to [email protected]. Users can establish extra forwarding rules as required. Step 2: Refresh the Aliases Database Refresh the aliases database to apply the modification using: sudo newaliases Step 3: Restart Postfix Lastly, restart again via: sudo systemctl restart postfix By sticking to these instructions, you may smoothly establish email forwarding, guaranteeing that mails intended for certain addresses are quickly forwarded to the selected account. Enabling SMTP Encryption Encrypting SMTP is a must to preserve the security and privacy of emails as they travel over the internet. Activating Transport Layer Security (TLS) strengthens the integrity of the communication path between the server and the mail client. Adhere to the below instructions to enable encryption: Step 1: Install Certbot First, the Certbot program needs to be installed to get a free TLS certificate from Let's Encrypt. The process of obtaining and renewing these certifications is made easier by Certbot, which can be installed on Ubuntu using: sudo apt install certbot Step 2: Allow HTTP Traffic Next, update the firewall settings to enable HTTP traffic on port 80 using the command provided below: sudo ufw allow 80 Step 3: Obtain a TLS Certificate Proceed by employing Certbot to acquire a TLS certificate for your domain. To achieve this, swap out your_domain with your real domain name in the command below: sudo certbot certonly --standalone --rsa-key-size 4096 --agree-tos --preferred-challenges http -d your_domain This command directs Certbot to: Use a 4096-bit RSA key to enhance security. Deploy a temporary independent server to carry out domain verification. Conduct the verification process through port 80. Adhere to the on-screen instructions and add your email address when prompted. After the process is finalized, Certbot will securely place your SSL certificate and private key within the /etc/letsencrypt/live/your_domain directory. Step 4: Postfix Configuration for TLS With the certificate in hand, update the settings to implement it by opening the configuration file via: sudo nano /etc/postfix/main.cf Find the TLS parameters part and update it to include these lines: # TLS parameterssmtpd_tls_cert_file=/etc/letsencrypt/live/your_domain/fullchain.pemsmtpd_tls_key_file=/etc/letsencrypt/live/your_domain/privkey.pemsmtpd_tls_security_level=maysmtp_tls_CApath=/etc/ssl/certssmtp_tls_security_level=maysmtp_tls_session_cache_database = btree:${data_directory}/smtp_scache Alter your_domain to your real domain's name; subsequently, the tool will be able to use the TLS certificate to safeguard email exchanges. Step 5: Restart Postfix To implement the modified settings, restart again using: sudo systemctl restart postfix Send the mail after finishing that, then check the recipient's mailbox. Unencrypted mails are more prone to being flagged as spam by email providers so the message might appear almost instantly. Following these guidelines can help you send mails safely and reduce the likelihood that email providers may mark them as spam. Conclusion Setting up Postfix through external SMTP servers is a simple process that enhances your server's email capabilities. This guide has thoroughly covered the installation, configuration, and testing phases of Postfix, including the setup of email forwarding and the activation of SMTP encryption. By adhering to these steps, users can ensure their mails are delivered securely.
09 December 2024 · 8 min to read
Go

Using Interfaces in Go

In object-oriented programming (OOP), the concept of interfaces plays a key role and is closely associated with one of the foundational principles—encapsulation. Interface as a Contract Simply put, an interface is a contract that defines the expected behavior between system components, such as how they exchange information. A real-world analogy for this concept can be seen in the Unix philosophy of "Everything is a file." This principle represents access to various resources—documents, peripherals, internal processes, and even network communication—as byte streams within the file system namespace. The advantage of this approach is that it allows a wide range of tools, utilities, and libraries to work uniformly with many types of resources. In OOP, an interface describes the structure of an object but leaves out implementation details. Interfaces in OOP Languages and Go Unlike languages like Java, C++, or PHP, Go is not a classically object-oriented language. When asked if Golang is OOP, the creators give an ambiguous answer: "Yes and no." While Go includes types and methods and supports an object-oriented programming style, it lacks class hierarchies (or even classes themselves), and the relationship between concrete and abstract (interface) types is implicit, unlike languages such as Java or C++. In traditional OOP languages, implementing an interface involves explicitly declaring that a class conforms to it (e.g., public class MyClass implements MyInterface). The implementing class must also define all methods described in the interface, matching their declared signatures exactly. In Go, there is no need for an explicit declaration that a type implements an interface. As long as a type provides definitions for all the methods specified in the interface, it is considered to implement that interface. In the Java example below, the class Circle is not an implementation of the interface Shape if the class description does not explicitly declare that it implements the interface, even if it contains methods matching those in Shape. In contrast, the class Square would be recognized as a Shape implementation because it explicitly declares so. // Shape.java interface Shape { public double area(); public double perimeter(); } // Circle.java public class Circle { private double radius; // constructor public Circle(double radius) { this.radius = radius; } public double area() { return this.radius * this.radius * Math.PI; } public double perimeter() { return 2 * this.radius * Math.PI; } } // Square.java public class Square implements Shape { private double x; // constructor public Square(double x) { this.x = x; } public double area() { return this.x * this.x; } public double perimeter() { return 4 * this.x; } } We can easily verify this by creating a function calculate that accepts an object implementing the Shape interface as an argument: // Calculator.java public class Calculator { public static void calculate(Shape shape) { double area = shape.area(); double perimeter = shape.area(); System.out.printf("Area: %f,%nPerimeter: %f."); } public static void main() { Square s = new Square(20); Circle c = new Circle(10); calculate(s); calculate(c); } } If we try to compile such code, we will get an error: javac Calculator.java Calculator.java:16: error: incompatible types: Circle cannot be converted to Shape calculate(c); ^ Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output 1 error In Golang, there is no requirement for a type to declare the interfaces it implements explicitly. It is sufficient to implement the methods described in the interface (the code below is adapted from Mihalis Tsoukalos's book "Mastering Go"): package main import ( "fmt" "math" ) type Shape interface { Area() float64 Perimeter() float64 } type Square struct { X float64 } func (s Square) Area() float64 { return s.X * s.X } func (s Square) Perimeter() float64 { return 4 * s.X } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return c.Radius * c.Radius * math.Pi } func (c Circle) Perimeter() float64 { return 2 * c.Radius * math.Pi } func Calculate(x Shape) { fmt.Printf("Area: %f,\nPerimeter: %f\n\n", x.Area(), x.Perimeter()) } func main() { s := Square{X: 20} c := Circle{Radius: 10} Calculate(s) Calculate(c) } Area: 400.000000, Perimeter: 80.000000 Area: 314.159265, Perimeter: 62.831853 If we try to use a type that does not implement the Shape interface as an argument for the Calculate function, we will get a compilation error. It is shown in the following example, where the Rectangle type does not implement the Shape interface (the Perimeter method is missing): package main import "fmt" type Shape interface { Area() float64 Perimeter() float64 } type Rectangle struct { W, H float64 } func (r Rectangle) Area() float64 { return r.W * r.H } func Calculate(x Shape) { fmt.Printf("Area: %f,\nPerimeter: %f\n\n", x.Area(), x.Perimeter()) } func main() { r := Rectangle{W: 10, H: 20} Calculate(r) } ./main.go:25:12: cannot use r (variable of type Rectangle) as type Shape in argument to Calculate: Rectangle does not implement Shape (missing Perimeter method) Notice how the Golang language compiler provides a more informative error message, unlike the Java language compiler. Problems and Solutions On the one hand, this approach to interface implementation simplifies writing programs, but on the other hand, it can become a source of errors that are sometimes hard to catch. Let’s look at an example. While working on a client library for a popular API, we needed to implement a caching mechanism — saving already retrieved data locally "on the client" to avoid repeated requests to the remote API server. API access was provided within packages that had a limited number of requests per month, so using a caching mechanism was economically beneficial for users. However, since the use cases for this library weren't limited to just web applications (although that was the most common scenario), we couldn't implement a single caching strategy that would satisfy everyone. Even in the case of applications running within a web server, there are at least two (or even all three) caching options — in-memory caching and using something like Memcached or Redis. However, there are also CLI (command-line interface) applications, and the caching strategies that work well for web applications are not suitable for command-line ones. As a result, we decided not to implement a single caching strategy, but to create our own interface listing methods for retrieving and storing data in the cache. We also wrote implementations of this interface for various caching strategies. This way, users of our library (other developers) could either use one of the implementations provided with the library or write their own custom implementation of the caching interface for their needs. Thus, the situation arose where the interface implementation and its application were separated into different codebases: the implementations were in "our" library, while the application of the interface was in other developers' applications. Our task was to check that our own implementations were indeed correct implementations of our own interface. Let's assume we have the cache.Interface interface and the cache.InMemory and cache.OnDisk types: package cache import ( "encoding/json" "fmt" "os" "sync" ) type Interface interface { Get(key string) (value []byte, ok bool) Set(key string, value []byte) Delete(key string) } type InMemory struct { mu sync.Mutex items map[string][]byte } func NewInMemory() *InMemory { return &InMemory{ items: make(map[string][]byte), } } func (c *InMemory) Get(key string) (value []byte, ok bool) { c.mu.Lock() value, ok = c.items[key] c.mu.Unlock() return value, ok } func (c *InMemory) Set(key string, value []byte) { c.mu.Lock() c.items[key] = value c.mu.Unlock() } func (c *InMemory) Delete(key string) { c.mu.Lock() delete(c.items, key) c.mu.Unlock() } type OnDisk struct { mu sync.Mutex items map[string][]byte filename string } func NewOnDisk(filename string) *OnDisk { return &OnDisk{ items: make(map[string][]byte), filename: filename, } } func (c *OnDisk) Get(key string) (value []byte, err error) { c.mu.Lock() defer c.mu.Unlock() f, err := os.Open(c.filename) if err != nil { return nil, err } defer f.Close() dec := json.NewDecoder(f) if err := dec.Decode(&c.items); err != nil { return nil, err } value, ok := c.items[key] if !ok { return nil, fmt.Errorf("no value for key: %s", key) } return value, nil } func (c *OnDisk) Set(key string, value []byte) error { c.mu.Lock() defer c.mu.Unlock() c.items[key] = value f, err := os.Create(c.filename) if err != nil { return err } enc := json.NewEncoder(f) if err := enc.Encode(c.items); err != nil { return err } return nil } func (c *OnDisk) Delete(key string) error { c.mu.Lock() defer c.mu.Unlock() delete(c.items, key) f, err := os.Create(c.filename) if err != nil { return err } enc := json.NewEncoder(f) if err := enc.Encode(c.items); err != nil { return err } return nil } Now we need to make sure that both of our types, cache.InMemory and cache.OnDisk, implement the cache.Interface. How can we achieve this? The first answer that comes to mind is to write a test. Test Let's write two small tests to check that our types cache.InMemory and cache.OnDisk implement the cache.Interface: package cache import "testing" func TestInMemoryImplementsInterface(t *testing.T) { var v interface{} = NewInMemory() _, ok := v.(Interface) if !ok { t.Error("InMemory does not implement Interface") } } func TestOnDiskImplementsInterface(t *testing.T) { var v interface{} = NewOnDisk("cache.json") _, ok := v.(Interface) if !ok { t.Error("OnDisk does not implement Interface") } } Let’s run these tests: go test -v ./cache === RUN TestInMemoryImplementsInterface --- PASS: TestInMemoryImplementsInterface (0.00s) === RUN TestOnDiskImplementsInterface cache_test.go:17: OnDisk does not implement Interface --- FAIL: TestOnDiskImplementsInterface (0.00s) FAIL FAIL cache 0.002s FAIL As seen from the test results, the cache.InMemory type implements the cache.Interface, but the cache.OnDisk type does not. But there is an easier way! While using tests to check for interface implementation works, it does require a certain level of discipline from the developer. You need to remember to write the tests and, just as importantly, to run them periodically. Fortunately, there is a simpler way to check whether a specific type implements the required interface. You only need to write a single line of code (for us, two lines, since we have two types) and run go build. package cache // ... var _ Interface = (*InMemory)(nil) var _ Interface = (*OnDisk)(nil) go build ./cache cache/cache.go:6:19: cannot use (*OnDisk)(nil) (value of type *OnDisk) as type Interface in variable declaration: *OnDisk does not implement Interface (wrong type for Delete method) have Delete(key string) error want Delete(key string) As you can see, the Golang compiler not only informs us that a type does not implement the interface, but also provides insight into the reason for this. In our case, it’s due to different method signatures. What is this magic? There’s no magic. The underscore symbol (_) is a special variable name used when we need to assign a value but do not intend to use it later. One of the most common uses of such variables is to ignore errors, for example: f, _ := os.Open("/path/to/file") In the above example, we open a file but do not check for potential errors. Thus, we create an unused variable of type cache.Interface and assign it a nil pointer to the implementation type (cache.InMemory or cache.OnDisk). Conclusion In this article, we explored the concept of an "interface" in different programming languages. We determined whether Go is an object-oriented language and learned how to check if a type implements an interface both via tests and during the compilation stage. On our app platform you can deploy Golang apps, such as Beego and Gin.
06 December 2024 · 10 min to read
Go

Introduction to Strings in Go

A string in Go is a basic data type that represents a simple sequence of bytes but comes with special methods for working with them. Golang provides the built-in strings package, which contains essential (and quite simple) functions for handling string data. These functions are similar to typical string functions in other programming languages like C or C++. In the examples in this article, we also use the fmt package to format and print strings to the console. Apart from defining strings, Go offers an extensive set of capabilities for performing various string manipulations. Declaring Strings It is worth mentioning that strings in Golang are somewhat different from those in Java, C++, or Python. A string in Go is a sequence of characters where each character can vary in size, which means it can be represented by one or more bytes in UTF-8 encoding. Historically, when the C language was developed, a character in a computer was represented by a 7-bit ASCII code. Thus, a string was essentially a collection of multiple 7-bit ASCII characters. However, as the use of computers grew globally, the 7-bit ASCII scheme became insufficient for supporting characters from different languages. This led to the development of various character encoding models like Unicode, UTF-8, UTF-16, UTF-32, etc. Different programming languages adopted their own character encoding schemes. For example, Java originally used UTF-16. On the other hand, Go is built on UTF-8 encoding. Thanks to UTF-8, Golang strings can contain universal text, representing a mix of any existing language in the world — without confusion or limitations. Additionally, strings in Go are immutable, meaning you cannot change their content after they are created. Declaring Strings with Double Quotes There are several common ways to declare (define) strings in Go: // Explicit declaration using "var" var variable1 = "some text" // Explicit declaration using "var" with a type specified var variable2 string = "peace" // Shorter declaration variable3 := "some text" You can also declare a string without an explicit value. In this case, the string variable is initialized with a zero value, which is an empty string: var variable4 string When double quotes are used to create a string variable, Golang interprets special (escaped) characters, written with a backslash (\). For example, \n represents a new line: import "fmt" ... var some_variable = "first line\nsecond line\nthird line" fmt.Println(some_variable) // Print the string to the console // OUTPUT: // first line // second line // third line Declaring Strings with Backticks To make the Go compiler ignore special characters and preserve the original formatting of strings, you can use backticks (`). Here’s an example of declaring a Go string with explicit formatting: import "fmt" ... // Line breaks in the variable are specified explicitly without adding special characters var some_variable = `first line second line third line` fmt.Println(some_variable) // Print the string to the console // OUTPUT: // first line // second line // third line Notice that the fmt package is used to output strings to the console. In this example, Golang completely ignores escaped characters: import "fmt" ... var some_variable = `upper line \n lower line` fmt.Println(some_variable) // Print the string to the console // OUTPUT: upper line \n lower line Modifying Strings in Go The purpose of the special String type is to allow working with more than just a "raw" sequence of bytes; it provides dedicated methods for managing strings. However, strictly speaking, strings are immutable in Go (unlike C and C++) — they cannot be changed. You can only access individual characters by their index: import "fmt" ... variable := "hello" c := variable[0] fmt.Printf("%c\n", c) // OUTPUT: h Despite this, there are many ways to create new strings from existing ones. Some functions for string manipulation require the strings package: import "strings" String Concatenation in Golang The most basic string manipulation is concatenating multiple strings into one. This is done using the + operator: import "fmt" ... var variable1 = "hello" var variable2 = "world" var space = " " var variable3 = variable1 + space + variable2 fmt.Println(variable3) // OUTPUT: hello world fmt.Println(variable2 + ", " + variable1) // OUTPUT: world, hello Note: You cannot add strings to other types, such as numbers: fmt.Println("I am " + 24 + " years old") // ERROR To make the above example work, you need to convert the number to a string using a type conversion function, such as strconv.Itoa: import "fmt" import "strconv" ... age := 24 fmt.Println("I am " + strconv.Itoa(age) + " years old") // OUTPUT: I am 24 years old fmt.Println("I am " + strconv.Itoa(24) + " years old") // OUTPUT: I am 24 years old Trimming Strings You can trim specific characters from the beginning and end of a string by specifying them as an argument: import ( "fmt" "strings" ) ... result := strings.Trim("xxxhello worldxxx", "xxx") fmt.Println(result) // OUTPUT: hello world Splitting Strings You can split a string into substrings by specifying a delimiter: import ( "fmt" "strings" ) ... result := strings.Split("hello world", " ") fmt.Println(result) // OUTPUT: [hello world Joining Strings You can join multiple Go strings stored in an array into a single string by explicitly specifying a delimiter: import ( "fmt" "strings" ) ... result := strings.Join([]string{"hello", "world"}, " ") // an array of strings is provided as an argument fmt.Println(result) // OUTPUT: hello world However, using a join function or the + operator for string concatenation is not always efficient. Each such operation creates a new string, which can reduce performance. To address this, Go provides an optimized tool for constructing strings from components while following specific rules — the Builder: import ( "fmt" "strings" ) ... builded := &strings.Builder{} builded.WriteString("very") builded.WriteString(" ") builded.WriteString("long") builded.WriteString(" ") builded.WriteString("line") fmt.Println(builded.String()) // OUTPUT: very long line For more details, refer to the official Golang documentation on the Builder type. Despite its powerful optimization capabilities, it is straightforward to use, as it doesn’t have a large number of methods. Splitting Strings You can split a string into parts by specifying a delimiter as an argument: import ( "fmt" "strings" ) ... result := strings.Split("h-e-l-l-o", "-") fmt.Println(result) // OUTPUT: [h e l l o] Replacing Substrings Go provides several ways to replace a substring with another: import ( "fmt" "strings" ) ... result := strings.Replace("hello", "l", "|", 1) // replace the first occurrence fmt.Println(result) // OUTPUT: he|lo result = strings.Replace("hello", "l", "|", -1) // replace all occurrences fmt.Println(result) // OUTPUT: he||o Changing String Case Go also provides methods to switch the case of a string — converting to uppercase or lowercase: import ( "fmt" "strings" ) ... fmt.Println(strings.ToUpper("hello")) // OUTPUT: HELLO fmt.Println(strings.ToLower("HELLO")) // OUTPUT: hello Creating a String from a Sequence of Bytes You can also convert a sequence of bytes into a full-fledged string and then work with it: import "fmt" ... // byte sequence any_bytes := []byte{0x47, 0x65, 0x65, 0x6b, 0x73} // create a string any_string := string(any_bytes) fmt.Println(any_string) // OUTPUT: Geeks Comparing Strings Searching for a Substring One way to check if a substring is present in a string is by using the strings.Contains function: import ( "fmt" "strings" ) ... result := strings.Contains("world", "rl") fmt.Println(result) // OUTPUT: true result = strings.Contains("world", "rrl") fmt.Println(result) // OUTPUT: false Classic Comparison Operators You can also use standard comparison operators to check for matches. These operators compare strings character by character in lexicographical order and consider the length of the strings: import "fmt" ... fmt.Println("hello" == "hello") // OUTPUT: true fmt.Println("hello" == "hello world") // OUTPUT: false fmt.Println("hello" > "hell") // OUTPUT: true fmt.Println("hello" > "lo") // OUTPUT: false Checking for Prefixes and Suffixes In addition to searching for substrings, you can check if a string contains a specific prefix or suffix using strings.HasPrefix and strings.HasSuffix: import ( "fmt" "strings" ) ... result := strings.HasPrefix("hello", "he") fmt.Println(result) // OUTPUT: true result = strings.HasSuffix("hello", "lo") fmt.Println(result) // OUTPUT: true result = strings.HasPrefix("hello", "el") fmt.Println(result) // OUTPUT: false Finding the Index of a Substring You can obtain the index of the first occurrence of a specified substring using the strings.Index function: import ( "fmt" "strings" ) ... result := strings.Index("hello", "el") fmt.Println(result) // OUTPUT: 1 result = strings.Index("hello", "le") fmt.Println(result) // OUTPUT: -1 If the substring is not found, the function returns -1. String Length To determine the length of a string in Golang, you can use the built-in len function: import "fmt" ... length := len("hello") fmt.Println(length) // OUTPUT: 5 Since Go uses UTF-8 encoding, the length of a string corresponds to the number of bytes, not the number of characters, as some characters may occupy 2 or more bytes. Iterating Over a String In some cases, such as comparing strings or processing their content, you may need to iterate through a string's characters manually.  This can be achieved using a for loop with a range clause: import "fmt" ... for symbol_index, symbol_value := range "Hello For All Worlds" { fmt.Printf("Value: %c; Index: %d\n", symbol_value, symbol_index) // additional actions can be performed here } This loop retrieves both the index and the value of each character in the string, making it easy to process each symbol individually. String Output and Formatting Formatting with Basic Types The fmt package in Go offers powerful tools for formatting strings during their output. Similar to other programming languages, Golang uses templates and annotation verbs for formatting. Here are some examples: import "fmt" ... // Formatting a string variable using %s any_string := "hello" result := fmt.Sprintf("%s world", any_string) fmt.Println(result) // OUTPUT: hello world // Formatting a number variable using %d any_number := 13 result = fmt.Sprintf("there are %d worlds!", any_number) fmt.Println(result) // OUTPUT: there are 13 worlds! // Formatting a boolean variable using %t any_boolean := true result = fmt.Sprintf("this is the %t world!", any_boolean) fmt.Println(result) // OUTPUT: this is the true world! The Sprintf function formats and returns the string, which can then be printed to the console using Println. Using Multiple Variables in Formatting You can use more complex templates to include multiple variables in the same format string: import "fmt" ... // Formatting two strings in one template first_string := "hello" second_string := "world" result := fmt.Sprintf("%s %s", first_string, second_string) fmt.Println(result) // OUTPUT: hello world // Formatting three numbers in one template first_number := 10 second_number := 20 third_number := 30 result = fmt.Sprintf("%d and %d and %d", first_number, second_number, third_number) fmt.Println(result) // OUTPUT: 10 and 20 and 30 // Formatting two boolean values in one template first_boolean := true second_boolean := false result = fmt.Sprintf("if it is not %t therefore it means it is %t", first_boolean, second_boolean) fmt.Println(result) // OUTPUT: if it is not true therefore it means it is false Mixing Different Variable Types You can combine variables of different types within a single formatted string: import "fmt" ... first_string := "hello" second_number := 13 third_boolean := true result := fmt.Sprintf("%s to all %d %t worlds", first_string, second_number, third_boolean) fmt.Println(result) // OUTPUT: hello to all 13 true worlds Formatting with Binary Data Go allows for special formatting of numbers into binary representation using %b: import "fmt" ... first_number := 13 second_number := 25 result := fmt.Sprintf("%b and %b", first_number, second_number) fmt.Println(result) // OUTPUT: 1101 and 11001 Conclusion Go provides a small yet sufficient toolkit for string manipulation, covering most of a developer's needs. One important concept to understand when working with Golang strings is that what we conventionally call "individual elements of a string" (characters) are actually sequences of UTF-8 bytes. This means that when working with strings, we are manipulating byte values. As a result, any attempt (which is prohibited in Go) to modify a two-byte character into a single byte would result in an error. Each time we "modify" a string, what we are actually doing is recreating it with updated values. Similarly, when we query the length of a string, we are retrieving the number of bytes used, not the number of characters. Nevertheless, Go's standard libraries are rich with functions for "manipulating" strings. This introductory article has demonstrated basic yet commonly used methods for interacting with strings in Golang. Keep in mind that in most cases, advanced string usage requires importing the specialized strings package and leveraging the fmt package to format strings for console output. For a complete and detailed reference of all available methods in the strings package, you can consult the official Go documentation. In addition,  you can deploy Go applications (such as Beego and Gin) on our app platform.
06 December 2024 · 11 min to read
Go

How to Use Templates in Go

Go (Golang) comes with a powerful, versatile templating system that allows for dynamic output generation, whether it's textual information (such as an email, document, or simply a console command) or entire web pages. Template processing in Go is based on two primary packages, each serving its own purpose: text/template html/template It’s important to note that both packages have an identical interface; however, the second (html/template) automatically protects HTML output from certain types of attacks, such as injections. Converting a Go template into final output is done by applying the template to the appropriate data structure. The input text for Go templates can be in any format and encoded in UTF-8. Template Entities A template is generally associated with a specific data structure (e.g., a struct) whose data will be inserted into the template. Thus, any template formally consists of three basic types of entities that "extract" the necessary variables and insert them into the output: ActionsThese are text fragments enclosed in curly braces {{ }}, where calculations or data substitutions take place. Actions make the content within the template dynamic by inserting the appropriate data. Actions can include simple variable substitutions, as well as loops or function executions that contribute to the final output. They directly control how the final result will appear. ConditionsConditions are the classic if-else constructions used within the template. Conditions allow you to include or exclude entire blocks of text from the final output, significantly enhancing the flexibility and capability of template-based content generation. LoopsLoops allow you to iterate over a collection of data, outputting multiple similar blocks but with different key information. These are useful when you need to generate repeated elements based on a list or array. Managing Templates in Go In Go, there are three most commonly used functions for managing templates: New: Creates a new template, which must be defined later. Parse: Analyzes the provided string containing the template text, and then returns a ready-to-use template. Execute: Executes the parsed template, applying the provided data structure, and writes the result to a specified variable. Additionally, there is the ParseFiles function, which allows you to process entire files containing the template's content rather than just a string. The following code demonstrates how to use these basic template functions in a simple scenario: package main // In addition to the template package, we import "os", which provides a platform-independent interface for interacting with the operating system. In this case, we'll use it to output the result of the template execution to the console. import ( "os" "text/template" ) // Define a struct whose data will be inserted into the template. type Person struct { Name string Age int } func main() { some_person := Person{"Alex", 32} // Instance of the previously defined struct some_template := "This is {{ .Name }} and he is {{ .Age }} years old" // Template text with embedded actions inside curly braces // Create a new template and parse its content, preparing it for further use ready_template, err := template.New("test").Parse(some_template) // Check for errors (nil means no error, similar to null in C) if err != nil { panic(err) // Stop execution and print the error } // Execute the template and print the result to the console err = ready_template.Execute(os.Stdout, some_person) // OUTPUT: This is Alex and he is 32 years old // Check for errors again if err != nil { panic(err) // Stop execution and print the error } } You can reuse a template "compiled" using the Parse function but with data from a different structure. For example, you could continue the main function from the above code like this: // Continuing from the previous code ... another_person := Person{"Max", 27} // Create another instance of the struct err = ready_template.Execute(os.Stdout, another_person) } In this case, the template will be reused and applied to the new instance (another_person), producing different output based on the new data. Note that inside a template, variables from the structure that was passed during execution are referenced within double curly braces {{ }}. When referring to these variables, the structure name is implicitly omitted and only the variable name is used, prefixed with a dot. For example: This is {{ .Name }} and he is {{ .Age }} years old You can also directly access the data passed during execution. For instance, the following code demonstrates how to pass simple text directly into the template: package main import ( "os" "text/template" ) func main() { some_template := "Here we have {{ . }}" ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, "no data, only text") // OUTPUT: Here we have no data, only text } In this example, the template simply inserts whatever value was passed to it (in this case, "no data, only text") without referencing a structure or fields within a structure. Template Syntax Features Static Text In the simplest case, a template can simply output static text without using any additional data: import ( "os" "text/template" ) ... some_template := "Just regular text" ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, "no data") // OUTPUT: Just regular text Static Text Inside Actions (Curly Braces) You can combine regular static text with additional data within curly braces: import ( "os" "text/template" ) ... some_template := "Not just regular text with {{ \"additional\" }} data" // Don't forget to escape the double quotes ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, "no data") // OUTPUT: Not just regular text with additional data Trimming Whitespace Markers You can use trimming markers before and after the curly braces to remove spaces: ... some_template := "Not just regular text with {{- \"additional\" -}} data" ... ready_template.Execute(os.Stdout, "no data") // OUTPUT: Not just regular text withadditionaldata // The output above isn't a typo — the spaces around "additional" have been removed Trimming markers remove not only a single space but multiple spaces on both sides of the text produced by the code inside the curly braces — both from the inside and outside. Numbers in Templates Unlike text, numbers are automatically inserted into the output without needing quotes: ... some_template := "Maybe this code was written by {{ 5 }} people." ... ready_template.Execute(os.Stdout, "no data") // OUTPUT: Maybe this code was written by 5 people. Similarly, trimming markers can be used with numbers as well: ... some_template := "Maybe this code was written by {{- 5 }} people." ... ready_template.Execute(os.Stdout, "no data") // OUTPUT: Maybe this code was written by5 people. Template Variables Golang allows you to define special variables that are only available within the template itself. Like in Go, a variable is defined by specifying its name and value, and then it is used. To define an internal variable, use $: package main import ( "os" "text/template" ) func main() { some_template := "First, we define a variable {{- $some_variable :=`Hello, I'm a variable` }}, then we use it: \"{{ $some_variable }}\"" ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, "no data") // OUTPUT: First, we define a variable, then we use it: "Hello, I'm a variable" } Note that to access the variable, we use $ because this variable is not related to any Go data structure, but is defined within the template itself. Conditional Expressions Go templates allow branching based on logic using the standard if/else conditional operators found in most programming languages: package main import ( "os" "text/template" ) func main() { some_template := "{{ if eq . `hello` -}} Hello! {{ else -}} Goodbye! {{ end }}" // We use a trimming marker after each condition to remove the leading space in the output ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, "hello") // OUTPUT: Hello! } In this example, the eq function is used (which stands for "equal") to compare the value passed to the template (accessed via the dot) with the string hello. Also, note that every conditional block is terminated with the end keyword. You can actually simplify the code by skipping the string comparison and directly passing a boolean variable, which makes the code more concise: package main import ( "os" "text/template" ) func main() { some_template := "{{ if . -}} Hello! {{ else -}} Goodbye! {{ end }}" ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, false) // OUTPUT: Goodbye! } Loops Templates are commonly used to output multiple similar data items, where the number of items changes from one output to another. This is where loops come in handy: package main import ( "os" "text/template" ) func main() { some_list := []string{"First", "Second", "Third"} some_template := "Let's count in order: {{ range .}}{{.}}, {{ end }}" ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, some_list) // OUTPUT: Let's count in order: First, Second, Third, } In this example, there's one issue—the last item in the list results in an extra comma followed by a space. To fix this, you can modify the code to check if the item is the last one in the list, ensuring that there is no comma and space after the last item: package main import ( "os" "text/template" ) func main() { some_list := []string{"First", "Second", "Third"} some_template := "Let's count in order: {{ range $index, $element := .}}{{ if $index }}, {{ end }}{{$element}}{{ end }}" ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, some_list) // OUTPUT: Let's count in order: First, Second, Third } In this modified example, two new variables are introduced—$index and $element—which are updated on each iteration of the loop. A comma and space are printed before each element, but only if the index ($index) is not zero. This ensures that the comma is not added before the first element. Template Functions Within Go templates, you can define and call custom functions that perform various operations on the passed arguments. However, before using them in a template, they need to be explicitly declared and registered. Here is an example: package main import ( "os" "text/template" ) func manipulate(first_arg, second_arg int) int { return first_arg + second_arg } func main() { some_list := []int{1, 2, 3} some_template := "Adding index and element in order: {{ range $index, $element := .}}{{ if $index }}, {{ end }}{{$index}} + {{$element}} = {{ do_manipulation $index $element }}{{ end }}" ready_template, err := template.New("test").Funcs(template.FuncMap{"do_manipulation": manipulate}).Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, some_list) // OUTPUT: Adding index and element in order: 0 + 1 = 1, 1 + 2 = 3, 2 + 3 = 5 } In this example, we intentionally renamed the Go function manipulate inside the template to do_manipulation. This is possible due to Go's flexibility. However, you could also use the original function name by simply registering it like this: ready_template, err := template.New("test").Funcs(template.FuncMap{"manipulate": manipulate}).Parse(some_template) This allows the custom function manipulate (or do_manipulation if renamed) to be used within the template for performing operations like addition on the index and element. Working with HTML Templates in Go As mentioned earlier, Go has an additional package for working with HTML templates: html/template. Unlike the standard text/template, this package protects applications from cross-site scripting (XSS) attacks, as Go ensures that data is safely rendered without allowing malicious content. Here’s how to import the necessary packages: import ( "html/template" "net/http" ) The net/http package is required to start an HTTP server on your local machine, which is necessary for testing the next example. HTML Template File It's best practice to store the template in a separate file. In this case, we'll create a file with the .html extension, although you can use any extension you prefer in your projects — Go does not impose any restrictions. We'll call the file index.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>{{ .Title }}</h1> <p> {{ .Text }} </p> </body> </html> Notice that we’ve specified two variables: Title and Text. Their values will be passed from a Go structure into the template. Minimal Go Code to Serve HTML Template Now let’s write the minimal Go code to run an HTTP server and send the rendered template result as a response to any request to the server: package main import ( "os" "html/template" "net/http" "log" ) // Declare the structure to store data for generating the template type Content struct { Title string Text string } // Function to handle HTTP requests to the server func generateResponse(writer http.ResponseWriter, request *http.Request) { if request.Method == "GET" { some_template, _ := template.ParseFiles("index.html") // Parse the template file some_content := Content{ Title: "This is the Title", Text: "This is the text content", } err := some_template.Execute(writer, some_content) // Execute the template, writing the output to the response writer if err != nil { panic(err) } } } func main() { // Start the HTTP server and use the generateResponse function to handle requests http.HandleFunc("/", generateResponse) err := http.ListenAndServe("localhost:8080", nil) if err != nil { log.Fatalln("Something went wrong:", err) } } Conclusion The Go programming language provides built-in support for creating dynamic content or rendering customizable output through templates. On our app platform you can deploy Golang apps, such as Beego and Gin.  This article covered the basic template functions that allow you to manage data dynamically, altering it according to a defined pattern — the template description itself. The implementation involves a few usage options: text/template html/template Remember that every template goes through three stages of formation, each of which is handled by a corresponding function: New: Creating the template. Parse: Analyzing (parsing) the template. Execute: Executing the template. This stage can be repeated indefinitely. You can refer to the official Go documentation on the text/template and html/template packages for more detailed information on the available functions and ways to use them.
05 December 2024 · 13 min to read
Go

How to Use the Cobra Package in Go

A Command-Line Interface (CLI) is a type of application that runs exclusively in the command-line terminal. Typically, such programs are used to manage various tools related to developing and maintaining network infrastructure. The interaction process is simple: The user types the name of the CLI application, the command name, parameters, and sometimes additional flags into the terminal. The CLI application performs the requested action and sends a text response to the terminal. CLI applications may seem outdated due to the lack of a graphical user interface (GUI), but they are still considered the most versatile, fast, and convenient way for system administration. Creating CLIs with Go and Cobra To create a CLI in Go, you can use a special package called Cobra, which is developed by third-party developers. It is built on top of the flag package (a command-line flag parser) from Go's standard library and provides a higher level of abstraction. Cobra is a complete CLI platform for the Go language, consisting of two main components: A library for creating modern CLI applications. A CLI tool for quickly building applications based on standard (for Cobra) command handler files. Cobra was originally developed by one of the Go team members, Steve Francia (spf13), for the Hugo project — a special framework for building websites. Over time, Cobra became one of the most popular packages in the Go community. Features of Cobra Cobra offers several simple features for building modern command-line interfaces. Additionally, Cobra includes a high-level controller to help organize the code for the CLI application being developed. Cobra implements: A command hierarchy Powerful argument and flag parsing Flag hierarchy (global and local) Subcommand checking POSIX compliance Automatic help generation for commands and flags In fact, large projects such as Kubernetes, Hugo, and CockroachDB are built on Go and use the Cobra package to handle commands. CLI commands follow a fairly standard pattern: {application} {command code} [arguments] [--flags and their parameters] For example, commands in real projects might look like this: kubectl get all -n kube-system Or like this: etcdctl put first second Cobra Architecture The working entities in Cobra can be divided into three types — each represents the structure of commands in the console terminal: Commands: These specify specific actions that need to be performed, much like in any classic CLI application. Arguments (Args): These are items or entities passed to a command, which the command works with and returns the result. Flags: Short modifiers for commands (i.e., specific actions) that make certain adjustments to the execution and affect the final result of the CLI application's operation. A Little About POSIX Compatibility The POSIX standard defines a pattern (scheme) for organizing arguments and flags that CLI applications should follow. This is the classic format that most developers are familiar with — numerous Linux utility programs (such as ls, cp, useradd) and third-party applications follow this convention. It is important to remember that the command scheme is strictly formalized in the standard and looks as follows: application_name [-a] [-b] [-c argument] [-d|-e] Each application may have multiple versions of the same option — long and short forms. There is a clear rule that the short version must consist of only one character. Step 1. Environment Setup Checking Go First, check whether the Go compiler is installed on your system. You can do this by running the version query command: go version If Go is installed, the console will display the Go version along with the operating system’s short name. Creating the Project Directory Next, create a separate directory for our Cobra project: mkdir CobraProject After that, navigate into it: cd CobraProject Golang has some peculiarities in its module system, which is necessary for connecting packages. Therefore, you need to initialize the project directory with a special command: go mod init CobraProject This will turn the directory into a full-fledged Go module, and the console will display a message about the creation of the module named CobraProject. Step 2. Installing the Cobra Package Downloading the Package from the Official Repository Starting from Go 1.18, Go includes a special command go install, which automatically installs remote modules. Therefore, we will use it to download the Cobra package from the official GitHub repository: go install github.com/spf13/cobra-cli@latest Note that with the @latest tag we are installing the latest release. Initializing the CLI After installation, the executable file cobra-cli will be available in the terminal. We will use this tool to initialize the Cobra project in our working directory — at this point, you should already be in that directory: cobra-cli init Once executed, this will create several files in your working directory, containing the standard Cobra package code along with the project name CobraProject. The file structure will look like this: CobraProject/ cmd/ root.go main.go go.mod go.sum The main.go file is the entry point for the CLI application. Its default content will look something like this: package main import ( "CobraProject/cmd" // the path may vary depending on the location of the working directory ) func main() { cmd.Execute() } All commands are placed as separate files in the /cmd directory.The root.go file is the root command handler — essentially the base command for any command-line interface. For example, consider the following command: go get URL Here, go is the root command, which is handled by root.go, and get is a subcommand, whose handler is placed in a file different from root.go. Building the CLI To build the CLI application, you use the same command as for building any regular Go binary project: go build By default, the executable file will appear in the project’s working directory. To make the built CLI application usable, you also need to install it: go install After this, the CLI application will be available for execution directly from the terminal. To use it, simply type the project name in the console: CobraProject If everything is set up correctly, the standard output for the command without any parameters will appear in the console. Of course, you can modify this standard output later in the root.go file. Step 3. Creating a Function for the Command Each command entered in the terminal calls a corresponding Go function, which executes the logic for that command. Any parameters and flags specified in the terminal are passed into the function. As a simple example, we will implement a small function that displays the time in the current time zone. To do this, we will use the time package. After initializing the CLI, the cmd directory should have been created in your working directory. Let's go to it: cd cmd Now, let's create a file that will contain our function: touch timefunc.go The code inside the file will look like this: package cmd // specify the name of our package import "time" // import the standard Go time package func getTimeFromZone(zone string) (string, error) { loc, err := time.LoadLocation(zone) // get the current location // check for error if err != nil { return "", err // return an empty result with error details } timeNow := time.Now().In(loc) // get the current time based on the location return timeNow.Format(time.RFC1123), nil // return the formatted result without error details } As you can see, the function returns two values: the result and any error data. You can use it in the CLI to retrieve the time for a specified time zone. Step 4. Adding a Command to the CLI Now that the functional part of our application is ready, we can "register" the command in the CLI application for external access. There is a separate add command for this: cobra-cli add timefromzone After running this command, a timefromzone.go file will appear in the cmd folder with the standard code inside. In this same folder, you will also find the root.go file, responsible for the "root" command processing, i.e., the command without any parameters. It’s easy to guess that the handlers for console commands are formed in the file system as separate Go source files. Let’s open the newly created file and populate it with the following code: package cmd import ( "fmt" "log" "github.com/spf13/cobra" ) var timefromzoneCmd = &cobra.Command{ Use: "timefromzone", Short: "Returns the time from a given geographical zone", Long: `This command returns the time from a specified geographical zone. It accepts only one argument — the zone for which the time is required. The result is returned in the RFC1123 format.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { timefromzone := args[0] timeNow, err := getTimeFromZone(timefromzone) if err != nil { log.Fatalln("Invalid time zone") } fmt.Println(timeNow) }, } func init() { rootCmd.AddCommand(timefromzoneCmd) // add the new command to the root command } Let’s break down what each field means in the command definition: Use: The name under which the command will be available in the terminal. Short: A brief description of the command, which will be displayed to the user in the console. Long: A full description of the command, which will be shown to the user in the console. Args: The exact number of arguments required for the command to function. Run: The handler function where we call and process the previously created getTimeFromZone function. In some cases, you could simplify the code by writing the logic directly inside the command handler function, like this: import "time" var timefromzoneCmd = &cobra.Command{ Use: "timefromzone", Short: "Returns the time from a given geographical zone", Long: `This command returns the time from a specified geographical zone. It accepts only one argument — the zone for which the time is required. The result is returned in RFC1123 format.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { zone := args[0] loc, err := time.LoadLocation(zone) if err != nil { log.Fatalln("Invalid time zone") } fmt.Println(time.Now().In(loc).Format(time.RFC1123)) }, } In this case, we directly implemented the logic for retrieving the time inside the Run function. If the time zone is invalid, an error message is logged. Once the command is added, we just need to reinstall our CLI application: go install Now, we can use the application from the terminal by specifying the command name and passing the time zone code as an argument: CobraProject timefromzone Europe/Nicosia The console output will look something like this: Sun, 10 Nov 2024 12:41:06 Europe/Nicosia You can find a complete list of time zones and their codes in Wikipedia. Step 5. Adding Flags to CLI Typically, when running command-line applications, you can specify flags in addition to parameters. Flags are options that modify the behavior of a specific command. They are easily recognized by the preceding hyphen (or double hyphen). The inclusion of flags in a CLI application adds variability and flexibility to the command behavior. Without flags, you would have to create many complex functions with a lot of redundant code. In this sense, flags help standardize the application. Cobra has two types of flags: Local flags: These only apply to the specific command. Persistent flags: These can apply  to all commands and subcommands. Let’s return to the timefromzone.go file and modify the initialization function to add a flag. The flag will specify the desired time format. Here’s how you can add the flag to your command: func init() { rootCmd.AddCommand(timefromzoneCmd) // Add the defined command to the root command timefromzoneCmd.Flags().String("format", "", "Outputs the time in the yyyy-mm-dd format") // Add a flag to the command } This adds a flag named --format, which specifies the time format. Here is the complete updated file with flag handling: package cmd import ( "fmt" "time" "github.com/spf13/cobra" ) var timefromzoneCmd = &cobra.Command{ Use: "timefromzone", Short: "Returns the time from a given geographical zone", Long: `This command returns the time from a specified geographical zone. It accepts only one argument — the zone for which the time is required. The result is returned in RFC1123 format.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { var date string zone := args[0] loc, _ := time.LoadLocation(zone) // Load the location from the zone argument fla, _ := cmd.Flags().GetString("format") // Get the value of the 'format' flag if fla != "" { date = time.Now().In(loc).Format(fla) // If flag is provided, use custom format } else { date = time.Now().In(loc).Format(time.RFC1123) // Default format } fmt.Printf("Current time in timezone %v: %v\n", loc, date) }, } func init() { rootCmd.AddCommand(timefromzoneCmd) // Add the timefromzone command to the root command timefromzoneCmd.Flags().String("format", "", "Outputs the time in the yyyy-mm-dd format") // Add the 'format' flag } Now, let's reinstall the updated CLI application: go install To use the new flag, run the command with the --format flag, like this: CobraProject timefromzone Europe/Nicosia --format 2006-01-02 The output will be formatted according to the flag, like this: Current time in timezone Europe/Nicosia: 2024-11-10 Here, the --format flag explicitly tells the command to display the time in the yyyy-mm-dd format, and the result will reflect this format. Conclusion The Cobra package for the Go programming language is an excellent solution that helps developers abstract away the complexities of low-level command-line parsing functions provided by the standard library. Cobra is a kind of framework for CLI applications that alleviates the "headache" developers face when working with the command-line terminal, allowing them to focus more on business logic. Each command is represented as a separate file in the /cmd directory, and you can modify it using flags. This is convenient because you can explicitly build a hierarchy of commands and control the process of handling them by editing hook functions like init or run. This feature gives Cobra CLI applications a more structured layout and less clutter, forming a solid framework. It’s important to note that third-party developers created Cobra, so it is hosted in a separate GitHub repository and is not part of the Go standard library. Additionally, on the official Cobra website, you can find installation instructions and details about using the command-line parser. On our cloud app platform you can deploy Golang apps, such as Beego and Gin.
05 December 2024 · 12 min to read

Answers to Your Questions

How can I get started with the DBaaS service?

It's as easy as placing an order in an online store. In the Hostman control panel, you can create a cloud database in just a couple of seconds. Simply select the appropriate DBMS and configuration, place an order, and you're up and running.
You don't need to customize the database environment and keep it up and running. Everything is already in place. Focus on important business tasks, and Hostman experts will take care of the maintenance!

What advantages does your service offer compared to installing and managing databases on my own servers?

Cloud databases are any resources you need for your data at arm's length. Cloud databases allow you to significantly reduce the labor costs of setup, administration, updating — all of this is either automated or our technical experts are ready to take care of it.

Are there any limitations on the number of databases or data volume in your pricing plans?

There is only one thing limiting you - the chosen tariff and the amount of resources that strictly corresponds to it. When you choose a tariff and create DBaaS — you create a cluster, within which you can create as many databases as you want. Each cluster is charged separately, and resources according to the tariff will be distributed among the databases it contains.

How is data security ensured in the DBaaS service?

Cloud databases are well protected from unauthorized access - only authorized users can access the data. User management takes place directly in the state-of-the-art Hostman control panel - no additional web interfaces are required.

We also guarantee 99.9% SLA uptime and place servers exclusively in the most reliable Tier IV data centers that meet all international security standards:

  • ISO: standards for data center design,
  • PCI DSS: payment data processing standards,
  • GDPR: European Union standards for personal data protection.

And you can store confidential data on a private local network and connect additional protection against DDoS attacks and other external threats.

Is data backup supported, and how often is it performed?

You can create database backups directly in the Hostman control panel: manually at any time or enable automatic backups once a day, once a week or once a month.

What scaling options are provided by your DBaaS service?

Unlike traditional databases, DBaaS can be scaled with ease — literally with a few clicks in the control panel. And if you need to reduce resources, contact our support — we'll get you up and running in no time.

Can I easily migrate my existing database to your service?

Request free help from Hostman engineers — create a database and then make a migration request via ticket. We will do everything quickly and in the best way.

How is monitoring and performance tracking of databases handled in DBaaS?

In the Hostman control panel there are several graphs that can be monitored at any time: CPU load, buffer usage, amount of free disk memory and so on.

What tools are provided for managing and administering databases?

You can use any familiar web interfaces for database management: Adminer, phpMyAdmin, etc. But it is most convenient to do it directly in the Hostman control panel.

In the Hostman control panel you can:

  • monitor load and resource consumption schedules,

  • add users and manage their access rights,

  • customize editing parameters,

  • connect extensions and increase the functionality of the database,

  • create backups, manage IP addresses, change tariffs and so on.

Are there any availability guarantees for databases in your DBaaS service?

Yes, we guarantee a 99.9% SLA level of data availability, which is explicitly stated in the offer. Thanks to Tier IV data centers, state-of-the-art server hardware and reliable Hostman technical support — your data will always be online.

Do you support automatic scaling of resources based on load?

We suggest reserving the required resources as part of the tariff. You can always reserve additional resources if you need to increase performance, and you will always pay only for the used resources. And you can lower the tariff and reduce the number of resources upon request to Hostman support.

What level of support do you offer, and how can assistance be obtained in case of issues?

The Hostman team includes experienced administrators and developers. You will always get the support you expect to receive. We're on call 24/7 via chat, mail, phone, and WhatsApp — and respond in minutes (or faster).

Do you have questions,
comments, or concerns?

Our professionals are available to assist you at any moment,
whether you need help or are just unsure of where to start.
Email us
Hostman's Support