diff --git a/convertQuery.go b/convertQuery.go
new file mode 100644
index 0000000000000000000000000000000000000000..4c5420a7525aabef852f3c1ea4a0e179ccbad3d8
--- /dev/null
+++ b/convertQuery.go
@@ -0,0 +1,322 @@
+package aql
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"git.science.uu.nl/datastrophe/aql-conversion/entity"
+)
+
+/*
+ConvertQuery converts a json string to an AQL query
+
+Parameters: jsonMsg is the JSON file directly outputted by the drag and drop query builder in the frontend
+
+Return: a string containing the corresponding AQL query, a string containing the database name and an error */
+func (s *Service) ConvertQuery(jsonMsg *[]byte) (*string, *string, error) {
+
+	jsonStruct, err := convertJSONToStruct(jsonMsg)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Check to make sure all indexes exist
+	// How many entities are there
+	numEntities := len(jsonStruct.Entities) - 1
+	// How many relations there are
+	numRelations := len(jsonStruct.Relations) - 1
+
+	// Make sure no entity should be returned that is outside the range of that list
+	for _, e := range jsonStruct.Return.Entities {
+		// If this entity references an entity that is outside the range
+		if e > numEntities || e < 0 {
+			return nil, nil, errors.New("non-existing entity referenced in return")
+		}
+	}
+
+	// Make sure that no relation mentions a non-existing entity
+	for _, r := range jsonStruct.Relations {
+		if r.EntityFrom > numEntities || r.EntityTo > numEntities {
+			return nil, nil, errors.New("non-exisiting entity referenced in relation")
+		}
+	}
+
+	// Make sure no non-existing relation is tried to be returned
+	for _, r := range jsonStruct.Return.Relations {
+		if r > numRelations || r < 0 {
+			return nil, nil, errors.New("non-existing relation referenced in return")
+		}
+	}
+
+	result := createQuery(jsonStruct)
+	return result, &jsonStruct.DatabaseName, nil
+}
+
+/* convertJSONtoStruct reads a JSON file and sorts the data into the appropriate structs
+Parameters: jsonMsg is the JSON file directly outputted by the drag and drop query builder in the frontend
+
+Return: parsedJSON is a struct with the same structure and holding the same data as jsonMsg
+*/
+func convertJSONToStruct(jsonMsg *[]byte) (*entity.QueryParsedJSON, error) {
+	jsonStruct := entity.QueryParsedJSON{}
+	err := json.Unmarshal(*jsonMsg, &jsonStruct)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &jsonStruct, 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.QueryParsedJSON) *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("LET %v = (\n\tFOR x IN %v \n", *name, node.Type)
+	footer := "\tRETURN x\n)\n"
+	constraints := *createConstraintStatements(&node.Constraints, "x", false)
+
+	ret := header + constraints + footer
+	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/convertQuery_test.go b/convertQuery_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..171c4ceb5b4ecd844a31e329505f25128c16d99e
--- /dev/null
+++ b/convertQuery_test.go
@@ -0,0 +1,833 @@
+package aql
+
+import (
+	"errors"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEmptyQueryConversion(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [],
+			"relations": []
+		},
+		"entities": [],
+		"relations": [],
+		"limit": 5000
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `
+LET nodes = first(RETURN UNION_DISTINCT([],[]))
+LET edges = first(RETURN UNION_DISTINCT([],[]))
+RETURN {"vertices":nodes, "edges":edges }`
+	assert.Equal(t, correctConvertedResult, *convertedResult)
+}
+
+func TestEntityOneAttributeQuery(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0
+			],
+			"relations": []
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [],
+		"limit": 5000
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET nodes = first(RETURN UNION_DISTINCT(n0,[],[]))LET edges = first(RETURN UNION_DISTINCT([],[]))RETURN {"vertices":nodes, "edges":edges }`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+
+func TestRelationWithConstraint(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0
+			],
+			"relations": [
+				0
+			]
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [
+			{
+				"type": "flights",
+				"depth": {
+					"min": 1,
+					"max": 1
+				},
+				"entityFrom": 0,
+				"entityTo": -1,
+				"constraints": [
+					{
+						"attribute": "Day",
+						"value": "15",
+						"dataType": "number",
+						"matchType": "EQ"
+					}
+				]
+			}
+		],
+		"limit": 5000
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER p.edges[*].Day ALL == 15 LIMIT 5000 RETURN DISTINCT p )LET nodes = first(RETURN UNION_DISTINCT(flatten(r0[**].vertices), [],[]))LET edges = first(RETURN UNION_DISTINCT(flatten(r0[**].edges), [],[]))RETURN {"vertices":nodes, "edges":edges }`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+
+func TestModifierCountEntity(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0
+			],
+			"relations": []
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [],
+		"limit": 5000,
+		"modifiers": [
+			{
+				"type": "COUNT",
+				"selectedType": "entity",
+				"id": 0,
+				"attributeIndex": -1
+			}
+		]
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)RETURN LENGTH (n0)`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+func TestModifierCountEntityAttribute(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0
+			],
+			"relations": []
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [],
+		"limit": 5000,
+		"modifiers": [
+			{
+				"type": "SUM",
+				"selectedType": "entity",
+				"id": 0,
+				"attributeIndex": 0
+			}
+		]
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)RETURN SUM (n0[*].state)`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+func TestModifierCountRelation(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0
+			],
+			"relations": [
+				0
+			]
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [
+			{
+				"type": "flights",
+				"depth": {
+					"min": 1,
+					"max": 1
+				},
+				"entityFrom": 0,
+				"entityTo": -1,
+				"constraints": [
+					{
+						"attribute": "Day",
+						"value": "15",
+						"dataType": "number",
+						"matchType": "EQ"
+					}
+				]
+			}
+		],
+		"limit": 5000,
+		"modifiers": [
+			{
+				"type": "COUNT",
+				"selectedType": "relation",
+				"id": 0,
+				"attributeIndex": -1
+			}
+		]
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER p.edges[*].Day ALL == 15 RETURN DISTINCT p )RETURN LENGTH (unique(r0[*].edges[**]))`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+func TestModifierCountRelationAttribute(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0
+			],
+			"relations": [
+				0
+			]
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [
+			{
+				"type": "flights",
+				"depth": {
+					"min": 1,
+					"max": 1
+				},
+				"entityFrom": 0,
+				"entityTo": -1,
+				"constraints": [
+					{
+						"attribute": "Day",
+						"value": "15",
+						"dataType": "number",
+						"matchType": "EQ"
+					}
+				]
+			}
+		],
+		"limit": 5000,
+		"modifiers": [
+			{
+				"type": "AVG",
+				"selectedType": "relation",
+				"id": 0,
+				"attributeIndex": 0
+			}
+		]
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER p.edges[*].Day ALL == 15 RETURN DISTINCT p )RETURN AVG (r0[*].edges[**].Day)`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+
+func TestRelationWithInOutConstraint(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0,
+				1
+			],
+			"relations": [
+				0
+			]
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "city",
+						"value": "San Francisco",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			},
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [
+			{
+				"type": "flights",
+				"depth": {
+					"min": 1,
+					"max": 3
+				},
+				"entityFrom": 1,
+				"entityTo": 0,
+				"constraints": [
+					{
+						"attribute": "Day",
+						"value": "15",
+						"dataType": "number",
+						"matchType": "EQ"
+					}
+				]
+			}
+		],
+		"limit": 5000
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n1 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET r0 = (FOR x IN n1 FOR v, e, p IN 1..3 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER v.city == "San Francisco" FILTER p.edges[*].Day ALL == 15 LIMIT 5000 RETURN DISTINCT p )LET nodes = first(RETURN UNION_DISTINCT(flatten(r0[**].vertices), [],[]))LET edges = first(RETURN UNION_DISTINCT(flatten(r0[**].edges), [],[]))RETURN {"vertices":nodes, "edges":edges }`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+
+func TestTwoRelations(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+		"return": {
+			"entities": [
+				0,
+				1,
+				2
+			],
+			"relations": [
+				0,
+				1
+			]
+		},
+		"entities": [
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "city",
+						"value": "New York",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			},
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "city",
+						"value": "San Francisco",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			},
+			{
+				"type": "airports",
+				"constraints": [
+					{
+						"attribute": "state",
+						"value": "HI",
+						"dataType": "text",
+						"matchType": "exact"
+					}
+				]
+			}
+		],
+		"relations": [
+			{
+				"type": "flights",
+				"depth": {
+					"min": 1,
+					"max": 3
+				},
+				"entityFrom": 2,
+				"entityTo": 1,
+				"constraints": [
+					{
+						"attribute": "Day",
+						"value": "15",
+						"dataType": "number",
+						"matchType": "EQ"
+					}
+				]
+			},
+			{
+				"type": "flights",
+				"depth": {
+					"min": 1,
+					"max": 1
+				},
+				"entityFrom": 0,
+				"entityTo": -1,
+				"constraints": []
+			}
+		],
+		"limit": 5000
+	}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n2 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET r0 = (FOR x IN n2 FOR v, e, p IN 1..3 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER v.city == "San Francisco" FILTER p.edges[*].Day ALL == 15 LIMIT 5000 RETURN DISTINCT p )LET n0 = (FOR x IN airports FILTER x.city == "New York" RETURN x)LET r1 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }LIMIT 5000 RETURN DISTINCT p )LET nodes = first(RETURN UNION_DISTINCT(flatten(r0[**].vertices), flatten(r1[**].vertices), [],[]))LET edges = first(RETURN UNION_DISTINCT(flatten(r0[**].edges), flatten(r1[**].edges), [],[]))RETURN {"vertices":nodes, "edges":edges }`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+
+func TestRelationWithOnlyToNode(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+			"return": {
+				"entities": [
+					0
+				],
+				"relations": [
+					0
+				]
+			},
+			"entities": [
+				{
+					"type": "airports",
+					"constraints": [
+						{
+							"attribute": "city",
+							"value": "San Francisco",
+							"dataType": "text",
+							"matchType": "exact"
+						}
+					]
+				}
+			],
+			"relations": [
+				{
+					"type": "flights",
+					"depth": {
+						"min": 1,
+						"max": 1
+					},
+					"entityFrom": -1,
+					"entityTo": 0,
+					"constraints": []
+				}
+			],
+			"limit": 5000
+		}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.city == "San Francisco" RETURN x)LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 INBOUND x flights OPTIONS { uniqueEdges: "path" }LIMIT 5000 RETURN DISTINCT p )LET nodes = first(RETURN UNION_DISTINCT(flatten(r0[**].vertices), [],[]))LET edges = first(RETURN UNION_DISTINCT(flatten(r0[**].edges), [],[]))RETURN {"vertices":nodes, "edges":edges }`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+
+func TestTooManyReturnEntities(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+			"return": {
+				"entities": [
+					0,
+					1,
+					2
+				],
+				"relations": [
+					0
+				]
+			},
+			"entities": [
+				{
+					"type": "airports",
+					"constraints": [
+						{
+							"attribute": "city",
+							"value": "San Francisco",
+							"dataType": "text",
+							"matchType": "exact"
+						}
+					]
+				}
+			],
+			"relations": [
+				{
+					"type": "flights",
+					"depth": {
+						"min": 1,
+						"max": 1
+					},
+					"entityFrom": -1,
+					"entityTo": 0,
+					"constraints": []
+				}
+			],
+			"limit": 5000
+		}`)
+
+	_, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.Equal(t, errors.New("non-existing entity referenced in return"), err)
+}
+
+func TestTooManyReturnRelations(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+			"return": {
+				"entities": [
+					0
+				],
+				"relations": [
+					0,
+					1,
+					2
+				]
+			},
+			"entities": [
+				{
+					"type": "airports",
+					"constraints": [
+						{
+							"attribute": "city",
+							"value": "San Francisco",
+							"dataType": "text",
+							"matchType": "exact"
+						}
+					]
+				}
+			],
+			"relations": [
+				{
+					"type": "flights",
+					"depth": {
+						"min": 1,
+						"max": 1
+					},
+					"entityFrom": -1,
+					"entityTo": 0,
+					"constraints": []
+				}
+			],
+			"limit": 5000
+		}`)
+
+	_, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.Equal(t, errors.New("non-existing relation referenced in return"), err)
+}
+
+func TestNegativeReturnEntities(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+			"return": {
+				"entities": [
+					0,
+					-1
+				],
+				"relations": [
+					0,
+					1,
+					2
+				]
+			},
+			"entities": [
+				{
+					"type": "airports",
+					"constraints": [
+						{
+							"attribute": "city",
+							"value": "San Francisco",
+							"dataType": "text",
+							"matchType": "exact"
+						}
+					]
+				}
+			],
+			"relations": [
+				{
+					"type": "flights",
+					"depth": {
+						"min": 1,
+						"max": 1
+					},
+					"entityFrom": -1,
+					"entityTo": 0,
+					"constraints": []
+				}
+			],
+			"limit": 5000
+		}`)
+
+	_, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.Equal(t, errors.New("non-existing entity referenced in return"), err)
+}
+
+func TestNoRelationsField(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+			"return": {
+				"entities": [
+					0
+				]
+			},
+			"entities": [
+				{
+					"type": "airports",
+					"constraints": [
+						{
+							"attribute": "city",
+							"value": "San Francisco",
+							"dataType": "text",
+							"matchType": "exact"
+						}
+					]
+				}
+			],
+			"limit": 5000
+		}`)
+
+	convertedResult, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+
+	// Assert that the result and the expected result are the same
+	correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.city == "San Francisco" RETURN x)LET nodes = first(RETURN UNION_DISTINCT(n0,[],[]))LET edges = first(RETURN UNION_DISTINCT([],[]))RETURN {"vertices":nodes, "edges":edges }`
+	cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "")
+	cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "")
+	assert.Equal(t, correctConvertedResult, cleanedResult)
+}
+
+func TestEntityFromLowerThanNegativeOneInRelation(t *testing.T) {
+	// Setup for test
+	// Create query conversion service
+	service := NewService()
+
+	query := []byte(`{
+			"return": {
+				"entities": [
+					0
+				],
+				"relations": [
+					0
+				]
+			},
+			"entities": [
+				{
+					"type": "airports",
+					"constraints": [
+						{
+							"attribute": "city",
+							"value": "San Francisco",
+							"dataType": "text",
+							"matchType": "exact"
+						}
+					]
+				}
+			],
+			"relations": [
+				{
+					"type": "flights",
+					"depth": {
+						"min": 1,
+						"max": 1
+					},
+					"entityFrom": -4,
+					"entityTo": 0,
+					"constraints": []
+				}
+			],
+			"limit": 5000
+		}`)
+
+	_, _, err := service.ConvertQuery(&query)
+
+	// Assert that there is no error
+	assert.NoError(t, err)
+}
diff --git a/createConstraints.go b/createConstraints.go
new file mode 100644
index 0000000000000000000000000000000000000000..abe8035f0045fbf19acc5a65aacb067b9c731cd0
--- /dev/null
+++ b/createConstraints.go
@@ -0,0 +1,100 @@
+package aql
+
+import (
+	"fmt"
+
+	"git.science.uu.nl/datastrophe/aql-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 := "\tFILTER"
+
+	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
+	)
+
+	// Constraint datatypes back end
+	// text		  MatchTypes: EQ/NEQ/contains/excludes
+	// number     MatchTypes: EQ/NEQ/GT/LT/GET/LET
+	// bool       MatchTypes: EQ/NEQ
+
+	switch constraint.DataType {
+	case "text":
+		value = fmt.Sprintf("\"%s\"", constraint.Value)
+		switch constraint.MatchType {
+		case "NEQ":
+			match = "!="
+		case "contains":
+			match = "LIKE"
+			value = fmt.Sprintf("\"%%%s%%\"", constraint.Value)
+		case "excludes":
+			match = "NOT LIKE"
+			value = fmt.Sprintf("\"%%%s%%\"", constraint.Value)
+		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", name, constraint.Attribute, match, value)
+	}
+	return &line
+}
diff --git a/entity/document.go b/entity/document.go
new file mode 100644
index 0000000000000000000000000000000000000000..08ef36d21bf6d9943034e2080d49b8110e621266
--- /dev/null
+++ b/entity/document.go
@@ -0,0 +1,13 @@
+package entity
+
+// Document with Empty struct to retrieve all data from the DB Document
+type Document map[string]interface{}
+
+// GeneralFormat with Empty struct to retrieve all data from the DB Document
+type GeneralFormat map[string][]Document
+
+// ListContainer is a struct that keeps track of the nodes and edges that need to be returned
+type ListContainer struct {
+	NodeList []Document
+	EdgeList []Document
+}
diff --git a/entity/queryStruct.go b/entity/queryStruct.go
new file mode 100644
index 0000000000000000000000000000000000000000..14ecebe354f5bca3e54998917e697f9419ca9cf2
--- /dev/null
+++ b/entity/queryStruct.go
@@ -0,0 +1,60 @@
+package entity
+
+// QueryParsedJSON is used for JSON conversion of the incoming byte array
+type QueryParsedJSON struct {
+	DatabaseName string
+	Return       QueryReturnStruct
+	Entities     []QueryEntityStruct
+	Relations    []QueryRelationStruct
+	// Limit is for limiting the amount of paths AQL will return in a relation let statement
+	Limit     int
+	Modifiers []QueryModifierStruct
+}
+
+// QueryReturnStruct holds the indices of the entities and relations that need to be returned
+type QueryReturnStruct struct {
+	Entities  []int
+	Relations []int
+	//Modifiers []int
+}
+
+// QueryEntityStruct encapsulates a single entity with its corresponding constraints
+type QueryEntityStruct struct {
+	Type        string
+	Constraints []QueryConstraintStruct
+}
+
+// QueryRelationStruct encapsulates a single relation with its corresponding constraints
+type QueryRelationStruct struct {
+	Type        string
+	EntityFrom  int
+	EntityTo    int
+	Depth       QuerySearchDepthStruct
+	Constraints []QueryConstraintStruct
+}
+
+// QueryModifierStruct encapsulates a single modifier with its corresponding constraints
+type QueryModifierStruct struct {
+	Type           string // SUM COUNT AVG
+	SelectedType   string // node relation
+	ID             int    // ID of the enitity or relation
+	AttributeIndex int    // = -1 if its the node or relation, = > -1 if an attribute is selected
+}
+
+// QuerySearchDepthStruct holds the range of traversals for the relation
+type QuerySearchDepthStruct struct {
+	Min int
+	Max int
+}
+
+// QueryConstraintStruct holds the information of the constraint
+// Constraint datatypes
+// 	text     MatchTypes: exact/contains/startswith/endswith
+// 	number   MatchTypes: GT/LT/EQ
+// 	bool     MatchTypes: EQ/NEQ
+type QueryConstraintStruct struct {
+	Attribute string
+	Value     string
+	DataType  string
+	MatchType string
+}
diff --git a/go.mod b/go.mod
index 864120bcfb670a2664485a0af0280d2186ee2804..3a61254e0f4386dd9bb3bfb5c501a27bd42afac0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
 module git.science.uu.nl/datastrophe/aql-conversion
 
 go 1.16
+
+require github.com/stretchr/testify v1.7.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..b380ae44575c322793da20d51d562f1d8aef1b1d
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,10 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/queryConverter.go b/queryConverter.go
new file mode 100644
index 0000000000000000000000000000000000000000..9fa906bba7880cdb2273621aa7b8fd0fe9edbc2f
--- /dev/null
+++ b/queryConverter.go
@@ -0,0 +1,9 @@
+package aql
+
+// Service implements the QueryConverter interface (in the query service)
+type Service struct {
+}
+
+func NewService() *Service {
+	return &Service{}
+}