diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 86818558ea8c477d3a9e23a6f3e4207929f337f7..ef2b4637bff4438e58d9f1eaee1a699a9cf1752a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -27,6 +27,21 @@ coverage:
   stage: test
   script:
     - make coverage
+  after_script:
+    - mkdir coverage/$CI_COMMIT_BRANCH
+    - cp coverage/cover.html coverage/$CI_COMMIT_BRANCH
+    - mv coverage/$CI_COMMIT_BRANCH/cover.html coverage/$CI_COMMIT_BRANCH/index.html
+    # install openssh client and add ssh keys
+    - apt-get install openssh-client curl -y >/dev/null
+    - mkdir ~/.ssh/
+    - eval $(ssh-agent -s)
+    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
+    - chmod 600 ~/.ssh/id_rsa
+    - ssh-add ~/.ssh/id_rsa
+    - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
+    - chmod 644 ~/.ssh/known_hosts
+    - ssh -fN -L 1234:science-vs260.science.uu.nl:22 sivan@up.science.uu.nl
+    - scp -r -o StrictHostKeyChecking=no -P 1234 -i ~/.ssh/id_rsa coverage/$CI_COMMIT_BRANCH root@localhost:/datadisk/documentation-coverage/home/backend/query-service/features
   artifacts:
     untracked: false
     expire_in: 30 days
diff --git a/cmd/query-service/main.go b/cmd/query-service/main.go
index 35cbd8e4b90e217e2e2cec4c1f730780435154da..370e4e114914282b5a598e76833661721c26eee3 100644
--- a/cmd/query-service/main.go
+++ b/cmd/query-service/main.go
@@ -1,7 +1,31 @@
 package main
 
-import "fmt"
+import (
+	"fmt"
+	"query-service/internal/aql"
+	"query-service/internal/consumer"
+	"query-service/internal/errorhandler"
+)
 
 func main() {
-	fmt.Println("Helloooo world")
+
+	exchangeID := "query-requests"
+	routingKey := "aql-user-request"
+	consumer.StartConsuming(onMessageReceived, exchangeID, routingKey)
+}
+
+func onMessageReceived(jsonMsg *[]byte) {
+	// Retrieve JSON formatted string payload from msg
+
+	// Convert the json byte msg to an aql query string
+	aqlQuery, err := aql.ConvertJSONToAQL(jsonMsg)
+	errorhandler.FailWithError(err, "failed to parse incoming msg to AQL") // TODO: don't panic on error, send error message to client instead
+
+	fmt.Println(*aqlQuery)
+
+	// execute and retrieve result
+
+	// convert result to general (node-link (?)) format
+
+	// publish converted result
 }
diff --git a/go.mod b/go.mod
index f3327accf6defbb821bf912b6f145d451654ccdc..640347d1c26a99887c1f3c39ee6d7ea7fa0e90f7 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,9 @@
 module query-service
 
 go 1.15
+
+require (
+	github.com/rs/xid v1.3.0
+	github.com/streadway/amqp v1.0.0
+	github.com/thijsheijden/alice v0.1.5
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..33802db516b1ff981b34d69733c14df54d8b12a1
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,6 @@
+github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
+github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
+github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/thijsheijden/alice v0.1.5 h1:kOZHhLGSHMja77I/Wvd19M7nvxgEWVIJ4vk5antCKdQ=
+github.com/thijsheijden/alice v0.1.5/go.mod h1:UypS/UTucbp+fmG1JtmXGCKPnGKimAC9AgmJ8iGoLNo=
diff --git a/internal/aql/aql.go b/internal/aql/aql.go
new file mode 100644
index 0000000000000000000000000000000000000000..2c2bb495d7b0b56c93d6e7565e2a21cf204f1880
--- /dev/null
+++ b/internal/aql/aql.go
@@ -0,0 +1,293 @@
+package aql
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+)
+
+// Constraint datatypes
+// text		  MatchTypes: exact/contains/startswith/endswith
+// number   MatchTypes: GT/LT/EQ
+// bool     MatchTypes: EQ/NEQ
+
+// Ranges dus tussen 10 half 5 bijv.
+
+// Struct used for JSON conversion
+type parsedJSON struct {
+	Return    returnStruct
+	Nodes     []nodeStruct
+	Relations []relationStruct
+}
+
+type returnStruct struct {
+	Entities  []int
+	Relations []int
+}
+
+type nodeStruct struct {
+	NodeType    string
+	Constraints map[string]constraintStruct
+}
+type relationStruct struct {
+	RelationType string
+	NodeFrom     int
+	NodeTo       int
+	Depth        searchDepthStruct
+	Constraints  map[string]constraintStruct
+}
+type searchDepthStruct struct {
+	Min int
+	Max int
+}
+
+type constraintStruct struct {
+	Value     string
+	DataType  string
+	MatchType string
+}
+
+// ConvertJSONToAQL converts a json string to an AQL query
+func ConvertJSONToAQL(jsonMsg *[]byte) (*string, error) {
+
+	jsonStruct, err := convertJSONToStruct(jsonMsg)
+	if err != nil {
+		fmt.Println(err)
+		return nil, err
+	}
+
+	//Per node query
+	//	per constraint
+	//relations koppelen ze samen
+	//return statement
+
+	result := createAllNodesQuery(jsonStruct.Return.Entities, jsonStruct.Nodes)
+	return result, nil
+}
+
+func createAllNodesQuery(returnEntitiesIndices []int, nodes []nodeStruct) *string {
+	var result string
+	for nodeIndex := range returnEntitiesIndices {
+		nodeID := fmt.Sprintf("n%s", strconv.Itoa(nodeIndex))
+
+		nodeQueryString := *createNodeQuery(&nodes[nodeIndex], nodeID)
+
+		result += fmt.Sprintf(" \n%s", nodeQueryString)
+	}
+
+	return &result
+}
+
+// createNodeQuery converts the node part of the json to a subquery
+func createNodeQuery(node *nodeStruct, name string) *string {
+	/*
+		LET alices = (
+			FOR x IN female
+			FILTER x.name == "Alice" AND x.birth_year > 1997
+			RETURN x
+		)
+
+		NAAR -->
+
+		LET {NAAM**} = (
+			FOR x IN {NODETYPE}
+			FILTER x.{CONSTRAINT[0]} {{CONSTRAINT[0]}.MATCHTYPE} {CONSTRAINT[0].VALUE}
+			AND x.{CONSTRAINT[1]} {{CONSTRAINT[1]}.MATCHTYPE} {CONSTRAINT[1].VALUE}
+			RETURN x
+		)
+
+	*/
+
+	letStatement := fmt.Sprintf("LET %s = (\n", name)
+	forStatement := fmt.Sprintf("\tFOR x IN %s \n", node.NodeType)
+
+	// Generate all constraints as FILTER statements
+	first := true
+	filter := "\tFILTER "
+	for key, constraint := range node.Constraints {
+		constraint := createQueryConstraint(&constraint, key)
+
+		if first {
+			filter += fmt.Sprintf("\t%s ", *constraint)
+			first = false
+		} else {
+			filter += fmt.Sprintf("AND\n\t\t%s", *constraint)
+		}
+	}
+
+	returnStatement := "\n\tRETURN x\n)"
+
+	// Concatenate all the statements
+	result := letStatement + forStatement + filter + returnStatement
+	return &result
+}
+
+// Constraint datatypes
+// text		  MatchTypes: exact/contains/startswith/endswith
+// number   MatchTypes: GT/LT/EQ
+// bool     MatchTypes: EQ/NEQ
+
+// createQueryConstraint creates a sinlge line of AQL filtering/constraint
+func createQueryConstraint(con *constraintStruct, key string) *string {
+	//FILTER x.{CONSTRAINT[0]} {{CONSTRAINT[0]}.MATCHTYPE} {CONSTRAINT[0].VALUE}
+	//			name				mtch					val + dataType
+	var (
+		mtch string
+		val  string
+		line string
+	)
+
+	//Wicked switches letsgo
+	switch con.DataType {
+	case "text":
+		val = fmt.Sprintf("\"%s\"", con.Value)
+		switch con.MatchType {
+		case "contains":
+			mtch = "IN"
+		case "startswith":
+			mtch = "LIKE"
+			val = fmt.Sprintf("\"%s%%\"", con.Value)
+		case "endswith":
+			mtch = "LIKE"
+			val = fmt.Sprintf("\"_%s\"", con.Value)
+		default: //exact
+			mtch = "=="
+		}
+	case "number":
+		val = con.Value
+		switch con.MatchType {
+		case "GT":
+			mtch = ">"
+		case "LT":
+			mtch = "<"
+		case "GET":
+			mtch = ">="
+		case "LET":
+			mtch = "<="
+		default: //EQ
+			mtch = "=="
+		}
+	default: /*bool*/
+		val = con.Value
+		switch con.MatchType {
+		case "NEQ":
+			mtch = "!="
+		default: //EQ
+			mtch = "=="
+		}
+	}
+	line = fmt.Sprintf("x.%s %s %s", key, mtch, val)
+	return &line
+}
+
+func convertJSONToStruct(jsonMsg *[]byte) (*parsedJSON, error) {
+	jsonStruct := parsedJSON{}
+	err := json.Unmarshal([]byte(*jsonMsg), &jsonStruct)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &jsonStruct, nil
+}
+
+/*
+	desired output
+
+	WITH male
+	FOR a1 IN female
+		FILTER a1.name == "Alice"
+
+		(FOR r1v,r1e,r1p IN 1..1 OUTBOUND a1 relation
+			FILTER r1v.name == "Bob")
+			OR
+		(FOR r2v,r2e,r2p IN 1..1 OUTBOUND a1 relation
+			FILTER r2v.name == "Martin" && r2v.hasdog == true)
+																// constraints for Bob
+				OR r1v.name == "Martin"
+			FILTER r1e.type == "married"
+
+			FOR r2v,r2e,r2p IN 1..1 OUTBOUND r1v relation
+				FILTER r2v.name == "Figo"
+				FILTER r2e.type == "has_dog"
+
+				RETURN {a1,r1v,r2v}
+
+
+	of
+
+
+	LET alices = (
+		FOR x IN female
+			FILTER x.name == "Alice"
+			RETURN x
+	)
+
+	LET bobs = (
+		FOR x IN male
+			FILTER x.name == "Bob"
+			RETURN x
+	)
+
+	LET alices = (
+		FOR alice IN alices
+			FOR r1v,r1e,r1p IN 1..1 OUTBOUND alice relation
+				FILTER r1v IN bobs AND r1e.type == "married"
+				RETURN {alice}
+	)
+
+	LET alices = (
+		FOR alice IN alices
+			FOR r1v,r1e,r1p IN 1..1 OUTBOUND alice relation
+				FILTER r1v IN marting AND r1e.type == "friend"
+				RETURN {alice}
+	)
+
+		FOR a2 IN male
+			FILTER a2.name == "Bob"
+
+			FOR r1v,r1e,r1p IN 1..1 OUTBOUND a1 relation
+				FILTER r1v._id == a2._id
+				FILTER r1e.type == "married"
+				RETURN {a1,a2,r1e}
+
+*/
+/*
+{
+	"NodeType": "female",
+	"Constraints":
+		{
+			"name":       { "Value": "Alice", "DataType": "text",   "MatchType": "exact" },
+			"birth_year": { "Value": "1997",  "DataType": "number", "MatchType": "GT"    }
+		}
+	}
+
+	NAAR -->
+
+	LET alices = (
+		FOR x IN female
+			FILTER x.name == "Alice" AND x.birth_year > 1997
+			RETURN x
+	)
+
+*/
+
+//Nog een manier vinden om namen te syncen over de queries heen **
+
+// func forGlory() *constraintStruct {
+// 	var yeet constraintStruct
+// 	yeet = constraintStruct{
+// 		ConstraintName: "name",
+// 		Value:          "Alice",
+// 		MatchType:      "exact",
+// 		DataType:       "text",
+// 	}
+// 	return &yeet
+// }
+
+// func alsoForGlory() {
+// 	con := forGlory()
+
+// 	toPrint := createQueryConstraint(*con)
+// 	fmt.Println(*toPrint)
+// }
diff --git a/internal/consumer/consumer.go b/internal/consumer/consumer.go
new file mode 100644
index 0000000000000000000000000000000000000000..e7426811a31d075b0667764f2472d9594b24ceba
--- /dev/null
+++ b/internal/consumer/consumer.go
@@ -0,0 +1,50 @@
+package consumer
+
+import (
+	"query-service/internal/errorhandler"
+	"time"
+
+	"github.com/streadway/amqp"
+	"github.com/thijsheijden/alice"
+)
+
+// ConsumeMessageFunc is a function type to be called when a message is consumed
+type ConsumeMessageFunc func(*[]byte)
+
+// StartConsuming will start consuming messages
+// When a message is received the consumeMessage function will be called
+func StartConsuming(consumeMessage ConsumeMessageFunc, exchangeID string, routingKey string) {
+
+	// Create connection config using environment variables
+	// rabbitUser := os.Getenv("RABBIT_USER")
+	// rabbitPassword := os.Getenv("RABBIT_PASSWORD")
+	// rabbitHost := os.Getenv("RABBIT_HOST")
+	// rabbitPort, err := strconv.Atoi(os.Getenv("RABBIT_PORT"))
+	rabbitUser := "haha-test"
+	rabbitPassword := "dikkedraak"
+	rabbitHost := "192.168.178.158"
+	rabbitPort := 5672
+
+	config := alice.CreateConfig(rabbitUser, rabbitPassword, rabbitHost, rabbitPort, true, time.Minute*1, alice.DefaultErrorHandler)
+
+	// Open connection to broker
+	conn := alice.Connect(*config)
+
+	// Declare the exchange we want to bind to
+	exchange, err := alice.CreateDefaultExchange(exchangeID, alice.Direct)
+	if err != nil {
+		errorhandler.FailWithError(err, "failed to create exchange")
+	}
+
+	// Declare the queue we will consume from
+	queue := alice.CreateQueue(exchange, "", true, false, true, false, nil)
+
+	// Create the consumer
+	c, err := conn.CreateConsumer(queue, routingKey, alice.DefaultConsumerErrorHandler)
+	if err != nil {
+		errorhandler.FailWithError(err, "failed to create consumer")
+	}
+
+	// Start consuming messages
+	c.ConsumeMessages(nil, false, func(msg amqp.Delivery) { consumeMessage(&msg.Body) })
+}
diff --git a/internal/errorhandler/errorhandler.go b/internal/errorhandler/errorhandler.go
new file mode 100644
index 0000000000000000000000000000000000000000..98047a959e69fdc6e191e2aab660a858be6bf8d3
--- /dev/null
+++ b/internal/errorhandler/errorhandler.go
@@ -0,0 +1,20 @@
+package errorhandler
+
+import (
+	"fmt"
+)
+
+// LogError logs an error that is not nil
+func LogError(err error, msg string) {
+	if err != nil {
+		fmt.Printf("%s: %v", msg, err)
+	}
+}
+
+// FailWithError panics if the error is not nil
+func FailWithError(err error, msg string) {
+	if err != nil {
+		fmt.Printf("%s: %v", msg, err)
+		panic(err)
+	}
+}