Getting started with Micronaut and Neo4J(-bolt) in kotlin

Richard Rijnberk
4 min readSep 15, 2020

--

Not too long ago, I decided to work on a personal project through which I wanted to learn more about micronaut, kotlin, and neo4j. And even though setting up micronaut in kotlin, or a neo4j server, isn’t all that difficult if you follow the documentation, combining the two is not as clearly explained as I would’ve liked.

This post aims to explain the initial set-up, connection, data storage, and finally, data retrieval in a manner I hope is more transparent.

You will need both micronaut and neo4j installed to follow the steps in this guide. I will not be covering their installation and configuration.

The initial project set-up:

If you do not yet have a project running, opening a terminal and running the following command sets up a new project with everything configured.

mn create-app neo4j-app — lang=kotlin — features neo4j-bolt

If you already have a project running and want to use the neo4j-bolt module, add the following lines to the dependencies of your build.gradle file:

implementation(“io.micronaut.neo4j:micronaut-neo4j-bolt”)testRuntime(“org.neo4j.test:neo4j-harness”)

Then configure the neo4j connection application.yml by adding the following lines:

neo4j:
uri: bolt://localhost:7687
maxConnectionPoolSize: 50
connectionAcquisitionTimeout: 30s
username: dev
password: dev

Note that I’ve added a user dev with password dev to my local neo4j database. This is purely for development and blogging purposes.

After the initial set-up

If you get an error stating “Could not find io.micronaut.neo4j:micronaut-neo4j-bolt:3.0.2”, you can edit the Gradle properties and downgrade to micronaut 2.0.1. Downgrading fixed the issue for me.

Creating your first controller

Every journey starts with the first step. In this case, the first step is creating a controller. Let’s go ahead and create a new kotlin class named “MyController.” I, as a personal preference, add it to a controller package.

Go ahead and copy the following code:

package neo4j.app.controllersimport io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
@Controller(“/”)
class MyController {
@Get(“/health”)
@Produces(MediaType.TEXT_PLAIN)
fun index(): String {
return “My Controller is online.”
}
}

You can now run the application and visit http://localhost:8080/health, which should tell you “My Controller is online.”.

Adding a model

In order to be able to communicate, we first require a model. Let’s create a MyNode under domain containing the following:

package neo4j.app.domain

class MyNode {
var name: String = ""

constructor(data: Map<*, *>) {
this.name = data["name"] as String
}
}

Adding a repository

Now we have our model we want to be able to store it in our neo4j graph. For this we need a repository, so let’s create a MyRepository under repository containing the following:

package neo4j.app.repository

import io.micronaut.context.annotation.Primary
import neo4j.app.domain.MyNode
import org.neo4j.driver.Driver
import javax.inject.Inject
import javax.inject.Singleton

@Primary
@Singleton
class MyRepository {
@Inject
var driver: Driver? = null

fun create(myNode: MyNode) {
driver!!.session().use { s ->
val statement = "CREATE (myNode:MyNode { name: '${myNode.name}' })"
s.writeTransaction { tx -> tx.run(statement) }
}
}
}

This creates a singleton for injection into our controller. If you look at the create function you can see that I execute a write transaction. This allows me to perform a write action on the graph; more on this later.

Wiring our repository to our controller

To wire the repository to the controller we need to inject the repo and create a new endpoint for posting our node data. Let’s open the MyController file and add the following to the top of the class:

@Inject
var repo: MyRepository? = null
@Post("/create")
fun create(myNode: MyNode): Number {
repo?.create(myNode)
return 200;
}

If we run our application now, and we send a post request to http://localhost:8080/create with the following payload we can check neo4j and find our new node has been stored.

Retrieving our node(s)

Now we have some information stored in our graph it’s time to retrieve it. To do this we have to extend our repository and add the following code. So let’s open the MyRepository file and add another function:

fun find(): Stream<MyNode?> {
driver!!.session().use { s ->
val statement = "MATCH (myNode:MyNode) RETURN myNode"
return s.readTransaction { tx: Transaction -> tx.run(statement)
.list<MyNode> { record: Record -> MyNode(record.get("myNode").asMap()) }.stream() }
}
}

If we look at the find function you can see I’m using a read transaction, this means that we’re retrieving information. It is important to ensure you’re using the correct transaction type as this is easily overlooked.

Now we have our repository function set up we have to wire a new endpoint to it. Like with the create function we’ll be adding a new endpoint to the controller. Open the MyController file and add the following function:

@Get("/list")
fun company(): Stream<MyNode?>? {
return repo?.find()
}

If we restart our application now and go to http://localhost:8080/list we get a list of stored nodes.

Summary

And that’s it. The basic set up to use neo4j with micronaut. From here on out it’s up to you to play with cypher and build whatever you want. I hope you found this guide useful. For reference I’ve checked in the app I wrote for this guide here.

--

--