Memgraph logo
Back to blog
Memgraph and Fiber Cookbook Recipe

Memgraph and Fiber Cookbook Recipe

October 5, 2023
Ante Javor

If you hop and take a look at the most popular and wanted languages in the 2023 Stack overflow survey, one of the common residents on the top-wanted list to learn for the past couple of years is the Go programming language. Since the inception of Go in 2009, the community growth has been phenomenal. The community growth made Go a standard language for a lot of different domains.

Web development is a popular and expansive domain that leads to different approaches when developing web-based applications and services based on Golang. Fiber is one of the web frameworks that is inspired by Express from the Node.js community. It was built on top of Fast HTTP. One of the driving factors behind the Fiber is the simplicity of use and performance built-in without complex memory management.

Ingredients used in this recipe

As with most web applications, you need a database to store data. Of course, that place will be filled in with Memgraph ❤️. Fiber has a nice repository called Fiber recipes. The idea behind it is to provide simple cookbook examples to start using the Fiber with other tools and technologies. In this particular case, it’s Memgraph and Fiber, but there are a few different examples there.

As with any typical web application, the flow goes from the user-facing front-end via the HTTP request to the backend. Then backend executes some type of action based on the request, in this example, it is Fiber handling the HTTP request and interacting with Memgraph. The driver used for connecting to Memgraph is the Neo4j Go driver since Neo4j and Memgraph both support Bolt protocol and Cypher drivers for databases can be used interchangeably.

Recipe walkthrough

As each recipe has a few steps, there are a few actions to take here. You can find an explanation of each step needed to be able to use Memgraph and Fiber. Keep in mind that this is a bare-bone structure for expansion. Full code can be found in the example in Fiber recipes repository. Feel free to contribute if you have ideas on how to expand it further.

Prerequisites

Before working on Fiber and Memgraph, you need to install two Go dependencies locally. It’s assumed that Docker and Go are present in your development environment. Dependencies include Neo4j Go driver and Fiber. You can achieve this by running install commands:

go get -u github.com/gofiber/fiber/v2
go get github.com/neo4j/neo4j-go-driver/v5

Once both dependencies are installed, you should now be able to import dependencies in your Go project in the expected manner:

import (
	"fmt"
	"log"
	"github.com/gofiber/fiber/v2"
	"github.com/neo4j/neo4j-go-driver/neo4j"
)

The next step is to start Memgraph with the following command:

docker run –name memgraph -it -p 7687:7687 -p 7444:7444 -p 3000:3000 -v mg_lib:/var/lib/memgraph memgraph/memgraph-platform

This will start the Memgaph platform in the Docker container and open port 7687 for a Bolt connection, there is also 7444 for logs and 3000 for visual user interface Memgraph Lab, but take a look at the further details on the Memgraph platform run guide.

Connecting to the database

Before doing any actual work in your application, you want to make sure that the Fiber app is initialized and that the backend is able to connect to Memgraph. The Fiber backend communicates with Memgraph via the driver, so the driver needs to be connected to the database.

So far, the main() should look something like this:

func main() {

	//Connect to Memgraph
	driver, err := ConnectDriverToDB()
	if err != nil {
		fmt.Print(err)
		panic(err)
	}
	defer driver.Close()

	//Create a Fiber app
	app := fiber.New()

	log.Fatal(app.Listen(":3000"))
}

The snippet for connecting the database and Neo4j Go driver, looks like this:

func ConnectDriverToDB() (neo4j.Driver, error) {
	//Memgraph communicates via Bolt protocol, using port 7687
	dbUri := "bolt://localhost:7687"
	var (
		driver neo4j.Driver
		err    error
	)
	if driver, err = neo4j.NewDriver(dbUri, neo4j.BasicAuth("", "", ""),
		func(conf *neo4j.Config) { conf.Encrypted = false }); err != nil {
		return nil, err
	}
	return driver, err
}

The connection to the Memgraph is done on Bolt port 7687. This is the same port that was opened on a docker container. The Bolt protocol and Cypher support are what make Memgraph and Neo4j compatible products. Since this is a prototype, auth data is not used.

Creating a session and running a query

After connecting to the database, in order to run a specific query, you will need to open a database session. Once the session is open, you will be able to execute a query. Keep in mind that a single session will be executed on a single Bolt worker that takes a single CPU thread. If you wish to run things concurrently, you need to open multiple sessions and distribute the load between different CPU threads. Memgraph supports concurrent execution of write and read queries.

The example below is the process for opening a single session with write access that is able to read or write to Memgraph. This code function takes a driver and a string as an argument, opens a session, executes a query, and returns the result:

func executeQuery(driver neo4j.Driver, query string) (neo4j.Result, error) {
	session, err := driver.Session(neo4j.AccessModeWrite)
	if err != nil {
		return nil, err
	}
	defer session.Close()

	result, err := session.Run(query, nil)
	if err != nil {
		return nil, err
	}
	return result, nil
}

Getting the data from the Memgraph

Once you are able to run different queries on Memgraph, it is time for the first actual request. In the recipe repository, there is a small graph dataset that is being added to Memgraph as the application is started. The graph has nodes describing Developers and technologies, and relationships represent what technology-specific developer loves. Here is a basic Cypher example used for creating this graph:

"CREATE (n:Developer {id: 1, name:'Andy'});",
"CREATE (n:Technology {id: 1, name:'Fiber'})",
"CREATE (n:Technology {id: 2, name:'Memgraph'})",
"CREATE (n:Technology {id: 3, name:'Go'})",
"CREATE INDEX ON :Developer(id);",
"CREATE INDEX ON :Technology(id);",
"MATCH (a:Developer {id: 1}),(b:Technology {id: 1}) CREATE (a)-[r:LOVES]->(b);",
“MATCH (a:Developer {id: 1}),(b:Technology {id: 2}) CREATE (a)-[r:LOVES]->(b);",
"MATCH (a:Developer {id: 1}),(b:Technology {id: 3}) CREATE (a)-[r:LOVES]->(b);",

Once data is in Memgraph, it means we can have an HTTP request that asks for a specific developer and technologies he likes. The complete request looks like this:

app.Get("/developer/:name", func(c *fiber.Ctx) error {
	name := c.Params("name")
	query := fmt.Sprintf(`MATCH (dev:Developer {name:'%s'})-[loves:LOVES]->(tech:Technology) RETURN dev, loves, tech `, name)

	result, err := executeQuery(driver, query)
	if err != nil {
		return err
	}

	developer := Developer{}
	loves := Loves{}
	technologies := []Technology{}
	technology := Technology{}

	for result.Next() {
		record := result.Record().Values()
		for _, value := range record {
			switch v := value.(type) {
			case neo4j.Node:
				if value.(neo4j.Node).Labels()[0] == "Developer" {
					developer.Id = value.(neo4j.Node).Props()["id"].(int64)
					developer.Label = value.(neo4j.Node).Labels()[0]
					developer.Name = value.(neo4j.Node).Props()["name"].(string)

				} else if value.(neo4j.Node).Labels()[0] == "Technology" {
					technology.Id = value.(neo4j.Node).Props()["id"].(int64)
					technology.Label = value.(neo4j.Node).Labels()[0]
					technology.Name = value.(neo4j.Node).Props()["name"].(string)
					technologies = append(technologies, technology)
				} else {
					fmt.Println("Unknown Node type")
				}
			case neo4j.Relationship:
				if value.(neo4j.Relationship).Type() == "LOVES" {
					loves.Label = value.(neo4j.Relationship).Type()
				} else {
					fmt.Println("Unknown Relationship type")
				}
			default:
				fmt.Printf("I don't know about type %T!\n", v)
			}

		}
	}

	data := map[string]interface{}{
		"developer":    developer,
		"loves":        loves,
		"technologies": technologies,
	}

	return c.JSON(data)

})

So to run this HTTP request back to Fiber, you will need to open your browser and hit the following URL:

http://localhost:3000/developer/Andy

Fiber Context will parse the URL argument, named Andy in this case, and inject it into our query:

query := fmt.Sprintf(`MATCH (dev:Developer {name:'%s'})-[loves:LOVES]->(tech:Technology) RETURN dev, loves, tech `, name)

Once the query is executed on Memgraph via the driver, the results in the format of Bolt records are returned. After that, there is a code structure for iterating over returned. By iteration over each record, the nodes and relationships are parsed and returned as JSON objects:

data := map[string]interface{}{
	"developer":    developer,
	"loves":        loves,
	"technologies": technologies,
}

return c.JSON(data)

Next steps with Fiber and Memgraph?

This recipe should be a great opportunity to jump-start a project with Memgraph, Fiber, and Go. If you need extra help, take a look at the Memgraph documentation. If you stumble upon any issues, join Discord community and ask away. It is always nice to have cool Gophers on board!

Join us on Discord!
Find other developers performing graph analytics in real time with Memgraph.
© 2024 Memgraph Ltd. All rights reserved.