diff --git a/cypher/convertQuery.go b/cypher/convertQuery.go
new file mode 100644
index 0000000000000000000000000000000000000000..059570d571937ec89a52bb64d98e18d52b30b59e
--- /dev/null
+++ b/cypher/convertQuery.go
@@ -0,0 +1,297 @@
+package cypher
+
+import (
+	"errors"
+	"fmt"
+
+	"git.science.uu.nl/datastrophe/query-conversion/entity"
+)
+
+/*
+ConvertQuery converts an IncomingQueryJSON object into AQL
+	JSONQuery: *entity.IncomingQueryJSON, the query to be converted to AQL
+	Returns: *string, the AQL query and a possible error
+*/
+func (s *Service) ConvertQuery(JSONQuery *entity.IncomingQueryJSON) (*string, error) {
+
+	// Check to make sure all indexes exist
+	// How many entities are there
+	numEntities := len(JSONQuery.Entities) - 1
+	// How many relations there are
+	numRelations := len(JSONQuery.Relations) - 1
+
+	// Make sure no entity should be returned that is outside the range of that list
+	for _, e := range JSONQuery.Return.Entities {
+		// If this entity references an entity that is outside the range
+		if e > numEntities || e < 0 {
+			return nil, errors.New("non-existing entity referenced in return")
+		}
+	}
+
+	// Make sure that no relation mentions a non-existing entity
+	for _, r := range JSONQuery.Relations {
+		if r.EntityFrom > numEntities || r.EntityTo > numEntities {
+			return nil, errors.New("non-exisiting entity referenced in relation")
+		}
+	}
+
+	// Make sure no non-existing relation is tried to be returned
+	for _, r := range JSONQuery.Return.Relations {
+		if r > numRelations || r < 0 {
+			return nil, errors.New("non-existing relation referenced in return")
+		}
+	}
+
+	result := createQuery(JSONQuery)
+	return result, nil
+}
+
+/* createQuery generates a query based on the json file provided
+Parameters: jsonQuery is a parsedJSON struct holding all the data needed to form a query
+
+Return: a string containing the corresponding AQL query and an error
+*/
+func createQuery(JSONQuery *entity.IncomingQueryJSON) *string {
+	// Note: Case #4, where there is an edge only query (without any entity), is not supported by frontend
+
+	// If a modifier is used, disable the limit
+	if len(JSONQuery.Modifiers) > 0 {
+		JSONQuery.Limit = -1
+	}
+
+	var (
+		relationsToReturn []string
+		nodesToReturn     []string
+		nodeUnion         string
+		relationUnion     string
+	)
+
+	// Loop over all relations
+	ret := ""
+
+	for i, relation := range JSONQuery.Relations {
+
+		relationName := fmt.Sprintf("r%v", i)
+
+		if relation.EntityFrom >= 0 {
+			// if there is a from-node
+			// create the let for this node
+			fromName := fmt.Sprintf("n%v", relation.EntityFrom)
+
+			ret += *createNodeLet(&JSONQuery.Entities[relation.EntityFrom], &fromName)
+
+			ret += *createRelationLetWithFromEntity(&relation, relationName, &JSONQuery.Entities, JSONQuery.Limit)
+		} else if relation.EntityTo >= 0 {
+			// if there is only a to-node
+			toName := fmt.Sprintf("n%v", relation.EntityTo)
+
+			ret += *createNodeLet(&JSONQuery.Entities[relation.EntityTo], &toName)
+
+			ret += *createRelationLetWithOnlyToEntity(&relation, relationName, &JSONQuery.Entities, JSONQuery.Limit)
+			// Add this relation to the list
+		} else {
+			fmt.Println("Relation-only queries are currently not supported")
+			continue
+		}
+
+		// Add this relation to the list
+		relationsToReturn = append(relationsToReturn, relationName)
+	}
+
+	// Add node let statements for nodes that are not yet returned
+	// Create a set from all the entity-from's and entity-to's, to check if they are returned
+	nodeSet := make(map[int]bool)
+	for _, relation := range JSONQuery.Relations {
+		nodeSet[relation.EntityFrom] = true
+		nodeSet[relation.EntityTo] = true
+	}
+
+	// Check if the entities to return are already returned
+	for _, entityIndex := range JSONQuery.Return.Entities {
+		if !nodeSet[entityIndex] {
+			// If not, return this node
+			name := fmt.Sprintf("n%v", entityIndex)
+			ret += *createNodeLet(&JSONQuery.Entities[entityIndex], &name)
+
+			// Add this node to the list
+			nodesToReturn = append(nodesToReturn, name)
+		}
+	}
+
+	//If there are modifiers within the query, we run a different set of checks which focus on quantifiable aspects
+	if len(JSONQuery.Modifiers) > 0 {
+		modifier := JSONQuery.Modifiers[0]
+		// There is a distinction between (relations and entities) and (relations or entities)
+		if len(JSONQuery.Return.Relations) > 0 && len(JSONQuery.Return.Entities) > 0 {
+
+			var pathDistinction string // .vertices or .edges
+
+			// Select the correct addition to the return of r0[**]
+			if modifier.SelectedType == "entity" {
+				// ASSUMING THERE IS ONLY 1 RELATION
+				if JSONQuery.Relations[0].EntityFrom == modifier.ID {
+					pathDistinction = fmt.Sprintf(".vertices[%v]", JSONQuery.Relations[0].Depth.Min-1)
+
+				} else {
+					pathDistinction = fmt.Sprintf(".vertices[%v]", JSONQuery.Relations[0].Depth.Max)
+
+				}
+			} else {
+				pathDistinction = ".edges[**]"
+			}
+
+			// Getting the attribute if there is one
+			if modifier.AttributeIndex != -1 {
+				if modifier.SelectedType == "entity" {
+					pathDistinction += fmt.Sprintf(".%v", JSONQuery.Entities[modifier.ID].Constraints[modifier.AttributeIndex].Attribute)
+
+				} else {
+					pathDistinction += fmt.Sprintf(".%v", JSONQuery.Relations[modifier.ID].Constraints[modifier.AttributeIndex].Attribute)
+
+				}
+			}
+
+			// If count is used it has to be replaced with Length + unique else use the modifier type
+			if modifier.Type == "COUNT" {
+				ret += fmt.Sprintf("RETURN LENGTH (unique(r0[*]%v))", pathDistinction)
+
+			} else {
+				ret += fmt.Sprintf("RETURN %v (r0[*]%v)", modifier.Type, pathDistinction)
+
+			}
+
+		} else {
+			// Check if the modifier is on an attribute
+			if modifier.AttributeIndex == -1 {
+				ret += fmt.Sprintf("RETURN LENGTH (n%v)", modifier.ID)
+			} else {
+				var attribute string
+
+				// Selecting the right attribute from either the entity constraint or relation constraint
+				if modifier.SelectedType == "entity" {
+					attribute = JSONQuery.Entities[modifier.ID].Constraints[modifier.AttributeIndex].Attribute
+
+				} else {
+					attribute = JSONQuery.Relations[modifier.ID].Constraints[modifier.AttributeIndex].Attribute
+
+				}
+
+				// If count is used it has to be replaced with Length + unique else use the modifier type
+				if modifier.Type == "COUNT" {
+					ret += fmt.Sprintf("RETURN LENGTH (unique(n%v[*].%v))", modifier.ID, attribute)
+
+				} else {
+					ret += fmt.Sprintf("RETURN %v (n%v[*].%v)", modifier.Type, modifier.ID, attribute)
+
+				}
+			}
+		}
+
+	} else {
+
+		// Create UNION statements that create unique lists of all the nodes and relations
+		// Thus removing all duplicates
+		nodeUnion = "\nLET nodes = first(RETURN UNION_DISTINCT("
+		for _, relation := range relationsToReturn {
+			nodeUnion += fmt.Sprintf("flatten(%v[**].vertices), ", relation)
+		}
+
+		for _, node := range nodesToReturn {
+			nodeUnion += fmt.Sprintf("%v,", node)
+		}
+		nodeUnion += "[],[]))\n"
+
+		relationUnion = "LET edges = first(RETURN UNION_DISTINCT("
+		for _, relation := range relationsToReturn {
+			relationUnion += fmt.Sprintf("flatten(%v[**].edges), ", relation)
+		}
+		relationUnion += "[],[]))\n"
+
+		ret += nodeUnion + relationUnion
+		ret += "RETURN {\"vertices\":nodes, \"edges\":edges }"
+
+	}
+
+	return &ret
+}
+
+/* createNodeLet generates a 'LET' statement for a node related query
+Parameters: node is an entityStruct containing the information of a single node,
+name is the autogenerated name of the node consisting of "n" + the index of the node
+
+Return: a string containing a single LET-statement in AQL
+*/
+func createNodeLet(node *entity.QueryEntityStruct, name *string) *string {
+	header := fmt.Sprintf("MATCH (%v : %v)\n", *name, node.Type)
+	constraints := *createConstraintStatements(&node.Constraints, *name, false)
+	ret := header + constraints
+	return &ret
+}
+
+/* createRelationLetWithFromEntity generates a 'LET' statement for relations with an 'EntityFrom' property and optionally an 'EntitiyTo' property
+Parameters: relation is a relation struct containing the information of a single relation,
+name is the autogenerated name of the node consisting of "r" + the index of the relation,
+entities is a list of entityStructs that are needed to form the relation LET-statement
+
+Return: a string containing a single LET-statement in AQL
+*/
+func createRelationLetWithFromEntity(relation *entity.QueryRelationStruct, name string, entities *[]entity.QueryEntityStruct, limit int) *string {
+	header := fmt.Sprintf("LET %v = (\n\tFOR x IN n%v \n", name, relation.EntityFrom)
+	forStatement := fmt.Sprintf("\tFOR v, e, p IN %v..%v OUTBOUND x %s \n", relation.Depth.Min, relation.Depth.Max, relation.Type)
+
+	// Guarantees that there is no path returned with a duplicate edge
+	// This way there are no cycle paths possible, TODO: more research about this needed
+	optionStmtn := "\tOPTIONS { uniqueEdges: \"path\" }\n"
+
+	vFilterStmnt := ""
+	if relation.EntityTo != -1 {
+		// If there is a to-node, generate the filter statement
+		toConstraints := (*entities)[relation.EntityTo].Constraints
+		vFilterStmnt += *createConstraintStatements(&toConstraints, "v", false)
+
+		// Add a WITH statement if the collection of entityTo is not yet included
+		if (*entities)[(*relation).EntityFrom].Type != (*entities)[(*relation).EntityTo].Type {
+			header = fmt.Sprintf("WITH %v\n %v", (*entities)[(*relation).EntityTo].Type, header)
+		}
+	}
+
+	relationFilterStmnt := *createConstraintStatements(&relation.Constraints, "p", true)
+
+	// Dont use a limit on quantifing queries
+	footer := ""
+	if limit != -1 {
+		footer += fmt.Sprintf("\tLIMIT %v \n", limit)
+	}
+	footer += "RETURN DISTINCT p )\n"
+
+	ret := header + forStatement + optionStmtn + vFilterStmnt + relationFilterStmnt + footer
+	return &ret
+}
+
+/* createRelationLetWithOnlyToEntity generates a 'LET' statement for relations with only an 'EntityTo' property
+Parameters: relation is a relation struct containing the information of a single relation,
+name is the autogenerated name of the node consisting of "r" + the index of the relation,
+entities is a list of entityStructs that are needed to form the relation LET-statement
+
+Return: a string containing a single LET-statement in AQL
+*/
+func createRelationLetWithOnlyToEntity(relation *entity.QueryRelationStruct, name string, entities *[]entity.QueryEntityStruct, limit int) *string {
+	header := fmt.Sprintf("LET %v = (\n\tFOR x IN n%v \n", name, relation.EntityTo)
+	forStatement := fmt.Sprintf("\tFOR v, e, p IN %v..%v INBOUND x %s \n", relation.Depth.Min, relation.Depth.Max, relation.Type)
+
+	// Guarantees that there is no path returned with a duplicate edge
+	// This way there are no cycle paths possible, TODO: more research about this needed
+	optionStmtn := "\tOPTIONS { uniqueEdges: \"path\" }\n"
+
+	relationFilterStmnt := *createConstraintStatements(&relation.Constraints, "p", true)
+
+	// Dont use a limit on quantifing queries
+	footer := ""
+	if limit != -1 {
+		footer += fmt.Sprintf("\tLIMIT %v \n", limit)
+	}
+	footer += "RETURN DISTINCT p )\n"
+
+	ret := header + forStatement + optionStmtn + relationFilterStmnt + footer
+	return &ret
+}
diff --git a/cypher/createConstraints.go b/cypher/createConstraints.go
new file mode 100644
index 0000000000000000000000000000000000000000..7bd06270da18c5ef96e1fee6a9cbab7150f7e9c5
--- /dev/null
+++ b/cypher/createConstraints.go
@@ -0,0 +1,104 @@
+package cypher
+
+import (
+	"fmt"
+
+	"git.science.uu.nl/datastrophe/query-conversion/entity"
+)
+
+/* createConstraintStatements generates the appropriate amount of constraint lines calling createConstraingBoolExpression
+Parameters: constraints is a list of constraintStructs that specify the constraints of a node or relation,
+name is the id of the corresponding relation/node,
+isRelation is a boolean specifying if this constraint comes from a node or relation
+
+Return: a string containing a FILTER-statement with all the constraints
+*/
+func createConstraintStatements(constraints *[]entity.QueryConstraintStruct, name string, isRelation bool) *string {
+	s := ""
+	if len(*constraints) == 0 {
+		return &s
+	}
+
+	newLineStatement := "\tWHERE"
+
+	for _, v := range *constraints {
+		s += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintBoolExpression(&v, name, isRelation))
+		newLineStatement = "\tAND"
+	}
+
+	return &s
+}
+
+/* createConstraintBoolExpression generates a single boolean expression,
+e.g. {name}.city == "New York".
+
+Parameters: constraint is a single constraint of a node or relation,
+name is the id of the corresponding relation/node,
+isRelation is a boolean specifying if this constraint comes from a node or relation, that changes the structure of the expression
+
+Return: a string containing an boolean expression of a single constraint
+*/
+func createConstraintBoolExpression(constraint *entity.QueryConstraintStruct, name string, isRelation bool) *string {
+	var (
+		match string
+		value string
+		line  string
+		neq   string
+	)
+
+	// Constraint datatypes back end
+	// text		  MatchTypes: EQ/NEQ/contains/excludes
+	// number     MatchTypes: EQ/NEQ/GT/LT/GET/LET
+	// bool       MatchTypes: EQ/NEQ
+
+	neq = ""
+
+	switch constraint.DataType {
+	case "text":
+		value = fmt.Sprintf("\"%s\"", constraint.Value)
+		switch constraint.MatchType {
+		case "NEQ":
+			match = "<>"
+		case "contains":
+			match = "CONTAINS"
+			value = fmt.Sprintf("\"%%%s%%\"", constraint.Value)
+		case "excludes":
+			match = "CONTAINS"
+			value = fmt.Sprintf("\"%%%s%%\"", constraint.Value)
+			neq = "NOT"
+		default: //EQ
+			match = "="
+		}
+	case "number":
+		value = constraint.Value
+		switch constraint.MatchType {
+		case "NEQ":
+			match = "<>"
+		case "GT":
+			match = ">"
+		case "LT":
+			match = "<"
+		case "GET":
+			match = ">="
+		case "LET":
+			match = "<="
+		default: //EQ
+			match = "="
+		}
+	default: /*bool*/
+		value = constraint.Value
+		switch constraint.MatchType {
+		case "NEQ":
+			match = "<>"
+		default: //EQ
+			match = "="
+		}
+	}
+
+	if isRelation {
+		line = fmt.Sprintf("%s.edges[*].%s ALL %s %s", name, constraint.Attribute, match, value)
+	} else {
+		line = fmt.Sprintf("%s %s.%s %s %s", neq, name, constraint.Attribute, match, value)
+	}
+	return &line
+}
diff --git a/cypher/queryConverter.go b/cypher/queryConverter.go
new file mode 100644
index 0000000000000000000000000000000000000000..1b0536f033d0d218777d633f84d0f67664b7fbb0
--- /dev/null
+++ b/cypher/queryConverter.go
@@ -0,0 +1,10 @@
+package cypher
+
+// Service implements the QueryConverter interface (in the query service)
+type Service struct {
+}
+
+// NewService creates a new AQL conversion service
+func NewService() *Service {
+	return &Service{}
+}
diff --git a/main/main.go b/main/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ccfa23f680b33600685dd2a7f1d226d50d76190
--- /dev/null
+++ b/main/main.go
@@ -0,0 +1,38 @@
+package main
+
+import(
+	"log"
+	"git.science.uu.nl/datastrophe/query-conversion/cypher/queryConverter.go"
+)
+
+func main(){
+	queryservice := new cypher.NewService()
+
+	js = `{
+		"return": {
+			"entities": [
+				0
+			],
+			"relations": []
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [],
+		"limit": 5000
+		}`
+	
+	
+	result, _ := cypher.convertQuery(JSON.marshal(js))
+	log.Println(result)
+}