Sunday 18 January 2015

Creating a graph of software technologies with Docker, Neo4j, NodeJS and D3.js Part 2

The plan

In the previous part we created a running Neo4j instance inside a Docker container.
Now we can create a middleware which will call the database's REST API, on the bottom of it's layer and expose an API to the client on the top of it's layer. As we did with the database instance, we will put the Node(s) into containers as well.

Ok, so let's create a Docker image for Node first.

The Node image

The good news is that there is an official Node image on Docker Hub. (Be aware, that does not mean we are super secure, it just means that this image should work properly). Let's check it's Dockerfile.
What we can see is that NPM is installed, so we can manage Node packages easily. Also if we run this image, the Node interpreter will be executed and will be waiting for input.
We are able to create our own image with a running Node + the packages we need.

We could put lots of "RUN npm install [package]" commands inside a Dockerfile, but instead of doing that, we will create package.json file and run "npm install" on it. It will be an external resource. If we run npm install, a node_modules directory will be created. It could be in a separate Docker image, and we could build a new image whenever we change the package.json, but I don't think it makes it too robust by putting the Node instance and the packages together into the same Docker image.

Ok let's do it.
Create the package.json file first.
I am using Express.js as a middleware to help me with the routing and HTTP requests, responses. There are loads of other middlewares to try out, but Express suits perfectly for now. So we will put Express into our package.json as a dependency.
I am also using the request Node module to make POST requests to our Neo4j instance.


Ok, now an app.js file.
I'll leave it empty for now.

Now next to the package.json let's create the Dockerfile.
So while building an image from this Dockerfile, a package.json and an app.js file will be added, and npm install will be called. It will create a node_modules directory and download into it the needed packages.

So, let's build it!
You can see it from the log that it downloaded what we need. Coolio!

The code

Now let's create a simple application which will handle adding a new technology, adding a new connection between two technologies and getting the adjacent technologies of a particular tech. Later we can add some logic for setting weights for connections to visualize how strong the connection is, and we can add weights to the techs as well to show how popular they are.

These are the API endpoints

POST:
/api/tech/new?name=[techname]

POST:
/api/connection/new?A=[Atechname]&B=[Btechname]&connection=[connection]

GET:
/api/tech/all

Let's open our app.js empty file and add our code.
The code is simple. Of course we would like to put the API endpoints, the function's and the configuration into separate files (modules) later, but in this stage we want to have simple solutions.

Ok. Now we need to rebuild our Docker image.


And run our web application. (Don't forget to stop-remove the old container.)

Notice that we are using --link neo4j:db. This creates some environment variables for us that we can use in our webapp for networking purpose.

Just because we are curious let's check these variables in our running webapp container.
Cool! We see that now we don't have to care what is the exact IP address of our Neo4j container. Docker will handle this for us, we just need to use the env variable.

Now we can create some technology nodes with curl or alternative tool for doing HTTP requests. I will execute these requests just to try it out:
  1. POST: http://0.0.0.0:3000/api/tech/new?name=HTML
  2. POST: http://0.0.0.0:3000/api/tech/new?name=FLASH
  3. POST: http://0.0.0.0:3000/api/connection/new?A=HTML&B=FLASH&RELATION=IS_RELATED_TO
  4. GET: http://0.0.0.0:3000/api/tech/all?technology=HTML
The last query returns  this JSON:
{"results":[{"columns":["(n1)-[*1]-(n2)"],"data":[{"row":[[[{"name":"HTML"},{},{"name":"FLASH"}]]]},{"row":[[[{"name":"HTML"},{},{"name":"FLASH"}]]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]},{"row":[[]]}]}],"errors":[]}


In the next part we will add the client code and actually draw our graph.

No comments:

Post a Comment