diff --git a/controller/northbound/server/nbi.go b/controller/northbound/server/nbi.go
index 040407e326693301a4e8050cf004185f70b60748..0cfae4ed240f193a95127d6643041c48a2b3b2d4 100644
--- a/controller/northbound/server/nbi.go
+++ b/controller/northbound/server/nbi.go
@@ -4,6 +4,9 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
 	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/nodes"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/ports"
 	"github.com/prometheus/client_golang/prometheus"
 	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc/codes"
@@ -13,32 +16,48 @@ import (
 var pndc networkdomain.PndStore
 var userService rbac.UserService
 var roleService rbac.RoleService
+var topologyService topology.Service
+var nodeService nodes.Service
+var portService ports.Service
 
 // NorthboundInterface is the representation of the
 // gRPC services used provided.
 type NorthboundInterface struct {
-	Pnd  *pndServer
-	Core *core
-	Csbi *csbi
-	Sbi  *sbiServer
-	Auth *Auth
-	User *User
-	Role *Role
+	Pnd      *pndServer
+	Core     *core
+	Csbi     *csbi
+	Sbi      *sbiServer
+	Auth     *Auth
+	User     *User
+	Role     *Role
+	Topology *Topology
 }
 
 // NewNBI receives a PndStore and returns a new gRPC *NorthboundInterface
-func NewNBI(pnds networkdomain.PndStore, users rbac.UserService, roles rbac.RoleService) *NorthboundInterface {
+func NewNBI(
+	pnds networkdomain.PndStore,
+	users rbac.UserService,
+	roles rbac.RoleService,
+	topology topology.Service,
+	node nodes.Service,
+	port ports.Service,
+) *NorthboundInterface {
 	pndc = pnds
 	userService = users
 	roleService = roles
+	topologyService = topology
+	nodeService = node
+	portService = port
+
 	return &NorthboundInterface{
-		Pnd:  &pndServer{},
-		Core: &core{},
-		Csbi: &csbi{},
-		Sbi:  &sbiServer{},
-		Auth: &Auth{},
-		User: &User{},
-		Role: &Role{},
+		Pnd:      &pndServer{},
+		Core:     &core{},
+		Csbi:     &csbi{},
+		Sbi:      &sbiServer{},
+		Auth:     &Auth{},
+		User:     &User{},
+		Role:     &Role{},
+		Topology: &Topology{},
 	}
 }
 
diff --git a/controller/northbound/server/topology.go b/controller/northbound/server/topology.go
new file mode 100644
index 0000000000000000000000000000000000000000..0f4c47e5d5e96fd2b1f6fa5fe91c9aef1c7b4632
--- /dev/null
+++ b/controller/northbound/server/topology.go
@@ -0,0 +1,127 @@
+package server
+
+import (
+	"context"
+	"time"
+
+	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/links"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/nodes"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/ports"
+	"github.com/google/uuid"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+// Topology holds a topologyService and represents a TopologyServiceServer.
+type Topology struct {
+	apb.UnimplementedTopologyServiceServer
+	topologyService topology.Service
+}
+
+// NewTopologyrServer receives a topologyService and returns a new TopologyServer.
+func NewTopologyrServer(service topology.Service) *Topology {
+	return &Topology{
+		topologyService: service,
+	}
+}
+
+// AddLink adds a new link to the topology
+func (t Topology) AddLink(ctx context.Context, request *apb.AddLinkRequest) (*apb.AddLinkResponse, error) {
+	sourceNode, sourcePort, err := t.ensureNodeAndPortExists(request.Link.SourceNode, request.Link.SourcePort)
+	if err != nil {
+		return nil, status.Errorf(codes.Aborted, "%v", err)
+	}
+
+	targetNode, targetPort, err := t.ensureNodeAndPortExists(request.Link.TargetNode, request.Link.TargetPort)
+	if err != nil {
+		return nil, status.Errorf(codes.Aborted, "%v", err)
+	}
+
+	link := links.Link{
+		ID:         uuid.New(),
+		Name:       request.Link.Name,
+		SourceNode: sourceNode,
+		SourcePort: sourcePort,
+		TargetNode: targetNode,
+		TargetPort: targetPort,
+	}
+	err = topologyService.AddLink(link)
+	if err != nil {
+		return nil, status.Errorf(codes.Aborted, "%v", err)
+	}
+
+	return &apb.AddLinkResponse{
+		Timestamp: time.Now().UnixNano(),
+		Status:    apb.Status_STATUS_OK,
+	}, nil
+}
+
+// GetTopology returns the current topology in the form of all links
+func (t Topology) GetTopology(ctx context.Context, request *apb.GetTopologyRequest) (*apb.GetTopologyResponse, error) {
+	topo, err := topologyService.GetAll()
+	if err != nil {
+		return nil, status.Errorf(codes.Aborted, "%v", err)
+	}
+
+	topology := &apb.Topology{}
+
+	for _, link := range topo {
+		topology.Links = append(topology.Links, &apb.Link{
+			Id:   link.ID.String(),
+			Name: link.Name,
+			SourceNode: &apb.Node{
+				Id:   link.SourceNode.ID.String(),
+				Name: link.SourceNode.Name,
+			},
+			SourcePort: &apb.Port{
+				Id: link.SourcePort.ID.String(),
+			},
+			TargetNode: &apb.Node{
+				Id:   link.TargetNode.ID.String(),
+				Name: link.TargetNode.Name,
+			},
+			TargetPort: &apb.Port{
+				Id: link.TargetPort.ID.String(),
+			},
+		})
+	}
+
+	return &apb.GetTopologyResponse{
+		Timestamp: time.Now().UnixNano(),
+		Status:    apb.Status_STATUS_OK,
+		Toplogy:   topology,
+	}, nil
+}
+
+func (t Topology) ensureNodeAndPortExists(incomingNode *apb.Node, incomingPort *apb.Port) (nodes.Node, ports.Port, error) {
+	node, err := nodeService.EnsureExists(
+		nodes.Node{
+			ID: getExistingOrCreateNewUUIDFromString(incomingNode.Id),
+		},
+	)
+	if err != nil {
+		return node, ports.Port{}, status.Errorf(codes.Aborted, "%v", err)
+	}
+
+	port, err := portService.EnsureExists(
+		ports.Port{
+			ID: getExistingOrCreateNewUUIDFromString(incomingPort.Id),
+		},
+	)
+	if err != nil {
+		return nodes.Node{}, port, status.Errorf(codes.Aborted, "%v", err)
+	}
+
+	return node, port, nil
+}
+
+func getExistingOrCreateNewUUIDFromString(id string) uuid.UUID {
+	parsedID, err := uuid.Parse(id)
+	if err != nil {
+		return uuid.Nil
+	}
+
+	return parsedID
+}