diff --git a/.gitignore b/.gitignore
index 40c560ef4d128872c96236e0bd6be3933b457645..d033570bd6117961e43109d831f212130ab83d50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ test/CMakeFiles/
 test/CTestTestfile.cmake
 test/Makefile
 test/cmake_install.cmake
+Testing/
diff --git a/include/graph_nt.hpp b/include/graph_nt.hpp
index 3b13ceec7c3d4ca8615649495e8aecc4a0653911..7ae9da37437ea1ce61ab08791053102deeb18abe 100644
--- a/include/graph_nt.hpp
+++ b/include/graph_nt.hpp
@@ -1,5 +1,5 @@
-#ifndef __GRAPH_HPP__
-#define __GRAPH_HPP__
+#ifndef __GRAPH_NT_HPP__
+#define __GRAPH_NT_HPP__
 
 #include <exception>
 #include <map>
@@ -59,39 +59,174 @@ class Graph {
 
 public:
 
-    Graph() { /*todo*/ }
-    Graph(const Graph &other) { /* todo */ }
+    Graph() {
+      id_counter = 0;
+      edge_counter = 0;
+    }
 
-    inline int add_node(const std::string &m) { /* todo */ return -1; }
+    Graph(const Graph &other) {
+        id_counter = other.id_counter;
+        edge_counter = other.edge_counter;
+        for (auto node : other.nodes) {
+            nodes[node.first] = node.second;
+        }
+        for (auto edge : other.edges) {
+            edges[edge.first] = edge.second;
+        }
+        for (auto dest : other.dests) {
+            dests[dest.first] = std::vector<int>();
+            for (auto d : dest.second) {
+                dests[dest.first].push_back(d);
+            }
+        }
+    }
 
-    inline bool node_exist(int id) const { /* todo */ return false; }
+    inline int add_node(const std::string &m) {
+      Node node;
+      node.node_id = id_counter;
+      node.data = m;
+      nodes[id_counter] = node;
+      dests[id_counter] = std::vector<int>();
+      return id_counter++;
+    }
 
-    inline int add_edge(const std::string &m, int source_id, int dest_id)
-        { /* todo */ return -1;}
-    
-    inline void remove_node(int node_id) { /* todo */ }
+    inline bool node_exist(int id) const {
+        if (nodes.find(id) == nodes.end()) {
+            return false;
+        }
+        return true;
+    }
+
+    inline int add_edge(const std::string &m, int source_id, int dest_id) {
+        if (!node_exist(source_id)) {
+            throw NodeNotFound(source_id);
+        }
+        if (!node_exist(dest_id)) {
+            throw NodeNotFound(dest_id);
+        }
+
+        Edge edge;
+        edge.edge_id = edge_counter;
+        edge.data = m;
+        edge.source_id = source_id;
+        edge.dest_id = dest_id;
+        edges[edge_counter] = edge;
+        dests[source_id].push_back(dest_id);
+        return edge_counter++;
+    }
+
+    inline void remove_node(int node_id) {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        for (auto edge_id : dests[node_id]) {
+            edges.erase(edge_id);
+        }
+        dests.erase(node_id);
+        nodes.erase(node_id);
+    }
 
-    inline int search_node(const std::string &m) const { /* todo */ return 0;}
+    inline int search_node(const std::string &m) const {
+        for (auto node : nodes) {
+            if (node.second.data == m) {
+                return node.first;
+            }
+        }
+        return -1;
+    }
     
-    inline std::string get_node_data(int node_id) const { /* todo */ return "TODO";}
+    inline std::string get_node_data(int node_id) const {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        return nodes.at(node_id).data;
+    }
 
-    inline std::string get_edge_data(int edge_id) const { /* todo */ return "TODO";}
+    inline std::string get_edge_data(int edge_id) const {
+        if (edges.find(edge_id) == edges.end()) {
+            throw EdgeNotFound(edge_id);
+        }
+        return edges.at(edge_id).data;
+    }
 
-    inline int get_edge_source(int edge_id) const { /* todo */ return -1;}
+    inline int get_edge_source(int edge_id) const {
+        if (edges.find(edge_id) == edges.end()) {
+            throw EdgeNotFound(edge_id);
+        }
+        return edges.at(edge_id).source_id;
+    }
     
-    inline int get_edge_dest(int edge_id) const { /* todo */ return -1;}
+    inline int get_edge_dest(int edge_id) const {
+        if (edges.find(edge_id) == edges.end()) {
+            throw EdgeNotFound(edge_id);
+        }
+        return edges.at(edge_id).dest_id;
+    }
 
-    std::vector<int> get_successors(int node_id) const { /* todo */ return std::vector<int>();}
+    std::vector<int> get_successors(int node_id) const {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        return dests.at(node_id);
+    }
     
-    std::vector<int> get_predecessors(int node_id) const { /* todo */ return std::vector<int>();}
+    std::vector<int> get_predecessors(int node_id) const {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        std::vector<int> preds;
+        // Visit all edges to find predecessors
+        for (auto edge : edges) {
+            if (edge.second.dest_id == node_id) {
+                preds.push_back(edge.second.source_id);
+            }
+        }
+        return preds;
+    }
 
     using Path=std::vector<int>;
 
     std::vector<Path> all_paths(int from, int to) const
-        { /* todo */ return std::vector<Path>();}
+        {
+            std::vector<Path> paths;
+            std::vector<int> visited;
+            Path path;
+            all_paths(from, to, visited, path, paths);
+            return paths;
+        }
+
+    void all_paths(int from, int to, std::vector<int> &visited, Path &path, std::vector<Path> &paths) const
+        {
+            visited.push_back(from);
+            path.push_back(from);
+            if (from == to) {
+                paths.push_back(path);
+            } else {
+                for (auto dest : get_successors(from)) {
+                    if (std::find(visited.begin(), visited.end(), dest) == visited.end()) {
+                        all_paths(dest, to, visited, path, paths);
+                    }
+                }
+            }
+            visited.pop_back();
+            path.pop_back();
+        }
 
     Path shortest_path(int from, int to) const
-        { /* todo */ return Path{};}
+        {
+
+            std::vector<Path> paths = all_paths(from, to);
+            if (paths.size() == 0) {
+                throw std::runtime_error("No path found");
+            }
+            Path shortest = paths[0];
+            for (auto path : paths) {
+                if (path.size() < shortest.size()) {
+                    shortest = path;
+                }
+            }
+            return shortest;
+        }
 };
 
 
diff --git a/include/graph_t.hpp b/include/graph_t.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7ef14dfd02e7e6dfbb45a807e4b3b819cfc72c5b
--- /dev/null
+++ b/include/graph_t.hpp
@@ -0,0 +1,235 @@
+#ifndef __GRAPH_T_HPP__
+#define __GRAPH_T_HPP__
+
+#include <exception>
+#include <map>
+#include <vector>
+#include <string>
+#include <algorithm>
+
+// Searches an element in the container, if it exists, it is removed
+template<class C, class E>
+void find_remove(C &cont, E elem)
+{
+    auto pos = cont.find(elem);
+    if (pos != end(cont))
+        cont.erase(pos);
+}
+
+// Exception : wrong node identifier
+class NodeNotFound {
+    const int id;
+public:
+    NodeNotFound(int node_id) : id(node_id) {}
+    std::string msg() const {
+        std::string m = "Node " + std::to_string(id) + " not found";
+        return m;
+    }
+};
+
+// Exception : wrong node identifier 
+class EdgeNotFound {
+    const int id;
+public:
+    EdgeNotFound(int edge_id) : id(edge_id) {}
+    std::string msg() const {
+        std::string m = "Edge " + std::to_string(id) + " not found";
+        return m;
+    }
+};
+
+template<class ND, class ED>
+class Graph {
+    struct Node {
+        int node_id;
+        ND data;
+    };
+    struct Edge {
+        int edge_id;
+        ED data;
+        int source_id;
+        int dest_id;
+    };
+
+    /* data structures */
+    std::map<int, Node> nodes;
+    std::map<int, Edge> edges;
+    std::map<int, std::vector<int>> dests;
+    int id_counter;
+    int edge_counter;
+
+public:
+
+    Graph() {
+      id_counter = 0;
+      edge_counter = 0;
+    }
+
+    Graph(const Graph &other) {
+        id_counter = other.id_counter;
+        edge_counter = other.edge_counter;
+        for (auto node : other.nodes) {
+            nodes[node.first] = node.second;
+        }
+        for (auto edge : other.edges) {
+            edges[edge.first] = edge.second;
+        }
+        for (auto dest : other.dests) {
+            dests[dest.first] = std::vector<int>();
+            for (auto d : dest.second) {
+                dests[dest.first].push_back(d);
+            }
+        }
+    }
+
+    inline int add_node(const ND &m) {
+      Node node;
+      node.node_id = id_counter;
+      node.data = m;
+      nodes[id_counter] = node;
+      dests[id_counter] = std::vector<int>();
+      return id_counter++;
+    }
+
+    inline bool node_exist(int id) const {
+        if (nodes.find(id) == nodes.end()) {
+            return false;
+        }
+        return true;
+    }
+
+    inline int add_edge(const ED &m, int source_id, int dest_id) {
+        if (!node_exist(source_id)) {
+            throw NodeNotFound(source_id);
+        }
+        if (!node_exist(dest_id)) {
+            throw NodeNotFound(dest_id);
+        }
+
+        Edge edge;
+        edge.edge_id = edge_counter;
+        edge.data = m;
+        edge.source_id = source_id;
+        edge.dest_id = dest_id;
+        edges[edge_counter] = edge;
+        dests[source_id].push_back(dest_id);
+        return edge_counter++;
+    }
+
+    inline void remove_node(int node_id) {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        for (auto edge_id : dests[node_id]) {
+            edges.erase(edge_id);
+        }
+        dests.erase(node_id);
+        nodes.erase(node_id);
+    }
+
+    inline int search_node(const ND &m) const {
+        for (auto node : nodes) {
+            if (node.second.data == m) {
+                return node.first;
+            }
+        }
+        return -1;
+    }
+    
+    inline ND get_node_data(int node_id) const {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        return nodes.at(node_id).data;
+    }
+
+    inline ED get_edge_data(int edge_id) const {
+        if (edges.find(edge_id) == edges.end()) {
+            throw EdgeNotFound(edge_id);
+        }
+        return edges.at(edge_id).data;
+    }
+
+    inline int get_edge_source(int edge_id) const {
+        if (edges.find(edge_id) == edges.end()) {
+            throw EdgeNotFound(edge_id);
+        }
+        return edges.at(edge_id).source_id;
+    }
+    
+    inline int get_edge_dest(int edge_id) const {
+        if (edges.find(edge_id) == edges.end()) {
+            throw EdgeNotFound(edge_id);
+        }
+        return edges.at(edge_id).dest_id;
+    }
+
+    std::vector<int> get_successors(int node_id) const {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        return dests.at(node_id);
+    }
+    
+    std::vector<int> get_predecessors(int node_id) const {
+        if (!node_exist(node_id)) {
+            throw NodeNotFound(node_id);
+        }
+        std::vector<int> preds;
+        // Visit all edges to find predecessors
+        for (auto edge : edges) {
+            if (edge.second.dest_id == node_id) {
+                preds.push_back(edge.second.source_id);
+            }
+        }
+        return preds;
+    }
+
+    using Path=std::vector<int>;
+
+    std::vector<Path> all_paths(int from, int to) const
+        {
+            std::vector<Path> paths;
+            std::vector<int> visited;
+            Path path;
+            all_paths(from, to, visited, path, paths);
+            return paths;
+        }
+
+    void all_paths(int from, int to, std::vector<int> &visited, Path &path, std::vector<Path> &paths) const
+        {
+            visited.push_back(from);
+            path.push_back(from);
+            if (from == to) {
+                paths.push_back(path);
+            } else {
+                for (auto dest : get_successors(from)) {
+                    if (std::find(visited.begin(), visited.end(), dest) == visited.end()) {
+                        all_paths(dest, to, visited, path, paths);
+                    }
+                }
+            }
+            visited.pop_back();
+            path.pop_back();
+        }
+
+    Path shortest_path(int from, int to) const
+        {
+
+            std::vector<Path> paths = all_paths(from, to);
+            if (paths.size() == 0) {
+                throw std::runtime_error("No path found");
+            }
+            Path shortest = paths[0];
+            for (auto path : paths) {
+                if (path.size() < shortest.size()) {
+                    shortest = path;
+                }
+            }
+            return shortest;
+        }
+};
+
+
+#endif
+
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 3cfed2a1ea97174776714221d0c379c658d627dd..2723260115b830d30a4952d4d0856d0191292837 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -5,7 +5,7 @@ Include(FetchContent)
 FetchContent_Declare(
   Catch2
   GIT_REPOSITORY https://github.com/catchorg/Catch2.git
-  GIT_TAG        v3.0.1)
+  GIT_TAG        v3.5.3)
 
 FetchContent_MakeAvailable(Catch2)
 
diff --git a/test/test_gnt b/test/test_gnt
new file mode 100755
index 0000000000000000000000000000000000000000..0d453e077b6a19f3275478b01f7baf167ba6c56b
Binary files /dev/null and b/test/test_gnt differ
diff --git a/test/test_gnt.cpp b/test/test_gnt.cpp
index fd14267880d826a3c870f192812db9c9d1b75d08..3dc00d10622a6bd1d47cf96438a8a9d725f316b8 100644
--- a/test/test_gnt.cpp
+++ b/test/test_gnt.cpp
@@ -1,12 +1,13 @@
 #include <catch2/catch_test_macros.hpp>
-#include "graph_nt.hpp"
+#include "graph_t.hpp"
+#include <iostream>
 
 using namespace std;
 
 SCENARIO("Graph properties", "[graph nt]")
 {
     GIVEN("A graph with some items") {
-        Graph g;
+        Graph<string, string> g;
         int a = g.add_node("A");
         int b = g.add_node("B");
         int c = g.add_node("C");
@@ -57,7 +58,7 @@ SCENARIO("Graph properties", "[graph nt]")
 SCENARIO("Computing paths", "[graph nt]")
 {
     GIVEN("A graph with some elements and no loops") {
-        Graph g;
+        Graph<string, string> g;
         int a = g.add_node("A");
         int b = g.add_node("B");
         int c = g.add_node("C");
@@ -79,4 +80,30 @@ SCENARIO("Computing paths", "[graph nt]")
     }
 }
 
-/** AJOUTER DES TESTS ... */
+SCENARIO("Shortest paths", "[graph nt]")
+{
+    GIVEN("A graph with some elements and no loops") {
+        Graph<string, string> g;
+        int a = g.add_node("A");
+        int b = g.add_node("B");
+        int c = g.add_node("C");
+        int d = g.add_node("D");
+        int e = g.add_node("E");
+
+        g.add_edge("ab", a, b);
+        g.add_edge("ac", a, c);
+        g.add_edge("bc", b, c);
+        g.add_edge("bd", b, d);
+        g.add_edge("cd", c, d);
+        g.add_edge("de", d, e);
+        g.add_edge("ce", c, e);
+
+        WHEN ("Computing all paths") {
+            auto paths = g.all_paths(a, e);
+            REQUIRE(paths.size() == 5);
+            REQUIRE(g.shortest_path(a,b).size() == 2);
+            REQUIRE(g.shortest_path(a,d).size() == 3);
+            REQUIRE(g.shortest_path(a,e).size() == 3);
+        }
+    }
+}