diff --git a/.gitignore b/.gitignore
index 97d369562649040bf024eb81640a1fe6832ef455..6cd12dfc66970786aeae95783b92af7e1d7b1b6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 target/**
 nbactions.xml
-
+nb-configuration.xml
+/target/
diff --git a/nb-configuration.xml b/nb-configuration.xml
deleted file mode 100644
index 71095dc1e2621d4ef23d14c3d97c6fb4f01ce556..0000000000000000000000000000000000000000
--- a/nb-configuration.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project-shared-configuration>
-    <!--
-This file contains additional configuration written by modules in the NetBeans IDE.
-The configuration is intended to be shared among all the users of project and
-therefore it is assumed to be part of version control checkout.
-Without this configuration present, some functionality in the IDE may be limited or fail altogether.
--->
-    <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
-        <!--
-Properties that influence various parts of the IDE, especially code formatting and the like. 
-You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
-That way multiple projects can share the same settings (useful for formatting rules for example).
-Any value defined here will override the pom.xml file value but is only applicable to the current project.
--->
-        <netbeans.hint.jdkPlatform>JDK_15</netbeans.hint.jdkPlatform>
-    </properties>
-</project-shared-configuration>
diff --git a/pom.xml b/pom.xml
index 49702203bfe476e39c985b61914adcc590240dec..93583c86712521d302f0c0f794f14a8556dd9305 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
     <artifactId>vospace-oats</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>vospace-oats</name>
-    <description>Demo project for Spring Boot</description>
+    <description>VOSpace REST service</description>
 
     <properties>
         <java.version>11</java.version>
@@ -36,28 +36,12 @@
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         
-        <!-- JAXB dependency -->
-        <dependency>
-            <groupId>javax.xml.bind</groupId>
-            <artifactId>jaxb-api</artifactId>
-        </dependency>
-        
-        <dependency>
-            <groupId>org.glassfish.jaxb</groupId>
-            <artifactId>jaxb-runtime</artifactId>
-        </dependency>
-        
         <!-- Jackson-JAXB compatibility -->
         <dependency>
             <groupId>com.fasterxml.jackson.module</groupId>
             <artifactId>jackson-module-jaxb-annotations</artifactId>
         </dependency>
         
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-actuator</artifactId>
-        </dependency>
-        
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
@@ -81,36 +65,46 @@
             <groupId>it.oats.inaf</groupId>
             <artifactId>vospace-datamodel</artifactId>
             <version>1.0-SNAPSHOT</version>
+            <exclusions>
+                <!-- Transitive dependency excluded to avoid duplicated dependency issues.
+                We want to use always the version provided by Spring Boot -->
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.module</groupId>
+                    <artifactId>jackson-module-jaxb-annotations</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
               
         <dependency>
             <groupId>it.inaf.ia2</groupId>
             <artifactId>auth-lib</artifactId>
             <version>2.0.0-SNAPSHOT</version>
-        </dependency>        
-              
+        </dependency>
+        
+        <!-- Embedded PostgreSQL: -->
         <dependency>
-            <groupId>it.inaf.ia2</groupId>
-            <artifactId>rap-client</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>     
-              
+            <groupId>com.opentable.components</groupId>
+            <artifactId>otj-pg-embedded</artifactId>
+            <version>0.13.3</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
-            <groupId>it.inaf.ia2</groupId>
-            <artifactId>gms-client</artifactId>
-            <version>1.0-SNAPSHOT</version>
+            <groupId>io.zonky.test.postgres</groupId>
+            <artifactId>embedded-postgres-binaries-linux-amd64</artifactId>
+            <version>12.5.0</version>
+            <scope>test</scope>
         </dependency>
-        
+
     </dependencies>
     
     
-  <repositories>
-    <repository>
-      <id>ia2-snapshots</id>
-      <name>your custom repo</name>
-      <url>http://repo.ia2.inaf.it/maven/repository/snapshots</url>
-    </repository>
-  </repositories>
+    <repositories>
+        <repository>
+            <id>ia2-snapshots</id>
+            <name>your custom repo</name>
+            <url>http://repo.ia2.inaf.it/maven/repository/snapshots</url>
+        </repository>
+    </repositories>
 
     <build>
         <plugins>
diff --git a/src/main/java/it/inaf/oats/vospace/ListNodeController.java b/src/main/java/it/inaf/oats/vospace/ListNodeController.java
index 1d6a906161d8ff7efb2f880220d1849b5c79073d..ed04f658d81a5a5d78dc5ae61010cec229ca191d 100644
--- a/src/main/java/it/inaf/oats/vospace/ListNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/ListNodeController.java
@@ -1,38 +1,43 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
-
 package it.inaf.oats.vospace;
 
-import java.util.List;
-
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.PathVariable;
 
 import net.ivoa.xml.vospace.v2.Node;
 
 import it.inaf.oats.vospace.persistence.NodeDAO;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.MediaType;
 
-    
 @RestController
 public class ListNodeController {
-    
+
     @Autowired
     private NodeDAO nodeDAO;
-    
-    @GetMapping(value="/{nodeName}")
-    public ResponseEntity<List<Node>>listNodes(@PathVariable("nodeName")String node_name) {
-        
-        // dal nome del nodo devo ricavarmi l'ivo_id
-        String node_ivo_id = "";
-        return ResponseEntity.ok(nodeDAO.listNode(node_ivo_id));
-        
+
+    @GetMapping(value = {"/nodes", "/nodes/**"},
+            produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_XML_VALUE})
+    public ResponseEntity<Node> listNode(HttpServletRequest request) {
+        String path = getPath(request);
+        return ResponseEntity.ok(nodeDAO.listNode(path));
+    }
+
+    /**
+     * Slash is a special character in defining REST endpoints and trying to
+     * define a PathVariable containing slashes doesn't work, so the endpoint
+     * has been defined using "/nodes/**" instead of "/nodes/{path}" and the
+     * path is extracted manually parsing the request URL.
+     */
+    private String getPath(HttpServletRequest request) {
+        String requestURL = request.getRequestURL().toString();
+        String[] split = requestURL.split("/nodes/");
+
+        String path = "/";
+        if (split.length == 2) {
+            path += split[1];
+        }
+        return path;
     }
- 
-    
 }
diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
index 38e6da8dfe7205b5bbb4a56f3579606e36cf7183..9dc64e7c455b56e60ad4d6040587d2f4b9761df3 100644
--- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
+++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
@@ -1,29 +1,17 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
-
 package it.inaf.oats.vospace.persistence;
 
 import net.ivoa.xml.vospace.v2.Node;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Types;
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
 import javax.sql.DataSource;
+import net.ivoa.xml.vospace.v2.ContainerNode;
+import net.ivoa.xml.vospace.v2.DataNode;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Repository;
-import org.springframework.http.ResponseEntity;
 
 /**
  *
@@ -32,6 +20,8 @@ import org.springframework.http.ResponseEntity;
 @Repository
 public class NodeDAO {
 
+    @Value("${vospace-authority}")
+    private String authority;
 
     private final JdbcTemplate jdbcTemplate;
 
@@ -41,8 +31,7 @@ public class NodeDAO {
     }
 
     public Node createNode(Node myNode) {
-        
-        
+
         StringBuilder sb = new StringBuilder();
         sb.append("INSERT INTO ");
         sb.append("NodeProperty");
@@ -50,26 +39,74 @@ public class NodeDAO {
         sb.append(" WHERE NOT EXISTS (SELECT * FROM NodeProperty");
         sb.append(" WHERE nodeID=? and propertyURI=?)");
         String sqlQuery = sb.toString();
-            
+
         return myNode;
     }
-    
-    
 
-    public List<Node> listNode(String nodeIvoId) {
+    public Node listNode(String path) {
 
-        String sql = "SELECT * FROM Node WHERE ivo_id=?";
+        String sql = "SELECT os.os_path, n.node_id, type, async_trans, owner_id, group_read, group_write, is_public, content_length, created_on, last_modified from node n\n"
+                + "JOIN node_os_path os ON n.node_id = os.node_id\n"
+                + "WHERE n.path ~ (" + getFirstLevelChildrenSelector(path) + ")::lquery\n"
+                + "OR os.os_path = ? ORDER BY os_path";
 
-        return jdbcTemplate.query(conn -> {
+        List<Node> parentAndChildren = jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
-            ps.setString(1, nodeIvoId);
+            ps.setString(1, path);
+            ps.setString(2, path);
             return ps;
         }, (row, index) -> {
-            Node newNode = new Node();
-            newNode.setUri(row.getString("ivo_id"));
-            return newNode;
+            return getNodeFromResultSet(row);
         });
 
+        // Query returns parent as first node
+        Node node = parentAndChildren.get(0);
+        
+        // Fill children
+        if (node instanceof ContainerNode && parentAndChildren.size() > 1) {
+            ContainerNode parent = (ContainerNode) node;
+            for (int i = 1; i < parentAndChildren.size(); i++) {
+                parent.getNodes().add(parentAndChildren.get(i));
+            }
+        }
+
+        return node;
+    }
+
+    private String getFirstLevelChildrenSelector(String path) {
+        String select = "(SELECT path FROM node WHERE node_id = (SELECT node_id FROM node_os_path WHERE os_path = ?))::varchar || '";
+
+        if (!"/".equals(path)) {
+            select += ".";
+        }
+        select += "*{1}'";
+        return select;
     }
 
+    private Node getNodeFromResultSet(ResultSet rs) throws SQLException {
+
+        Node node = getTypedNode(rs.getString("type"));
+        node.setUri(getUri(rs.getString("os_path")));
+
+        return node;
+    }
+
+    private Node getTypedNode(String type) {
+        Node node;
+        switch (type) {
+            case "container":
+                node = new ContainerNode();
+                break;
+            case "data":
+                node = new DataNode();
+                break;
+            default:
+                throw new UnsupportedOperationException("Node type " + type + " not supported yet");
+        }
+        return node;
+    }
+
+    private String getUri(String path) {
+        return "vos://" + authority + path;
+    }
 }
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 57546985c5acbf05a5a946d937a8e6592bf667f9..8b2697437b3ca10bdf9179f3c7f47b0dea7f31dc 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -19,9 +19,9 @@ server.servlet.context-path=/vospace
 # For development only:
 spring.profiles.active=dev
 
-spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/vospacedb
-spring.datasource.username=vospaceusr
-spring.datasource.password=Peper0ne
+spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/vospace_testdb
+spring.datasource.username=postgres
+spring.datasource.password=postgres
 
 # enable debug logging 
 # this is equivalent to passing '--debug' as command line argument
@@ -32,3 +32,5 @@ logging.level.org.springframework.web=TRACE
 #logging.level.org.springframework.boot=DEBUG
 # log to file (absolute/relative path of log file)
 #logging.file=path/to/log/file.log
+
+vospace-authority=example.com!vospace
diff --git a/src/test/java/it/inaf/oats/vospace/ListNodeControllerTest.java b/src/test/java/it/inaf/oats/vospace/ListNodeControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b8b5f3dc05fb6fdacbf4a98fcb94e2a5a9a87f4
--- /dev/null
+++ b/src/test/java/it/inaf/oats/vospace/ListNodeControllerTest.java
@@ -0,0 +1,57 @@
+package it.inaf.oats.vospace;
+
+import it.inaf.oats.vospace.persistence.NodeDAO;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import static org.mockito.ArgumentMatchers.eq;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+@ExtendWith(MockitoExtension.class)
+public class ListNodeControllerTest {
+
+    @Mock
+    private NodeDAO dao;
+
+    @InjectMocks
+    private ListNodeController controller;
+
+    private MockMvc mockMvc;
+
+    @BeforeEach
+    public void init() {
+        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+    }
+
+    @Test
+    public void testRootXml() throws Exception {
+
+        mockMvc.perform(get("/nodes")
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().isOk());
+
+        verify(dao, times(1)).listNode(eq("/"));
+    }
+
+    @Test
+    public void testNodeXml() throws Exception {
+
+        mockMvc.perform(get("/nodes/mynode")
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().isOk());
+
+        verify(dao, times(1)).listNode(eq("/mynode"));
+    }
+}
diff --git a/src/test/java/it/inaf/oats/vospace/persistence/DataSourceConfig.java b/src/test/java/it/inaf/oats/vospace/persistence/DataSourceConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9339550ee186ba8760c354dedee921e12c99c60
--- /dev/null
+++ b/src/test/java/it/inaf/oats/vospace/persistence/DataSourceConfig.java
@@ -0,0 +1,117 @@
+package it.inaf.oats.vospace.persistence;
+
+import com.opentable.db.postgres.embedded.EmbeddedPostgres;
+import com.opentable.db.postgres.embedded.PgBinaryResolver;
+import com.opentable.db.postgres.embedded.UncompressBundleDirectoryResolver;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.sql.DataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Scope;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+
+/**
+ * Generates a DataSource that can be used for testing DAO classes. It loads an
+ * embedded Postgres database and fills it using the data from
+ * vospace-transfer-service repository (folder must exists; it location can be
+ * configured using the init_database_scripts_path in test.properties).
+ */
+@TestConfiguration
+public class DataSourceConfig {
+
+    @Value("${init_database_scripts_path}")
+    private String scriptPath;
+
+    /**
+     * Using the prototype scope we are generating a different database in each
+     * test.
+     */
+    @Bean
+    @Scope("prototype")
+    @Primary
+    public DataSource dataSource() throws Exception {
+        DataSource embeddedPostgresDS = EmbeddedPostgres.builder()
+                .setPgDirectoryResolver(new UncompressBundleDirectoryResolver(new CustomPostgresBinaryResolver()))
+                .start().getPostgresDatabase();
+
+        initDatabase(embeddedPostgresDS);
+
+        return embeddedPostgresDS;
+    }
+
+    private class CustomPostgresBinaryResolver implements PgBinaryResolver {
+
+        /**
+         * Loads specific embedded Postgres version.
+         */
+        @Override
+        public InputStream getPgBinary(String system, String architecture) throws IOException {
+            ClassPathResource resource = new ClassPathResource(String.format("postgres-%s-%s.txz", system.toLowerCase(), architecture));
+            return resource.getInputStream();
+        }
+    }
+
+    /**
+     * Loads SQL scripts for database initialization from
+     * vospace-transfer-service repo directory.
+     */
+    private void initDatabase(DataSource dataSource) throws Exception {
+        try ( Connection conn = dataSource.getConnection()) {
+
+            File currentDir = new File(DataSourceConfig.class.getClassLoader().getResource(".").getFile());
+            Path scriptDir = currentDir.toPath().resolve(scriptPath);
+
+            List<String> scripts = Arrays.asList("00-init.sql", "01-pgsql_path.sql", "03-indexes.sql", "05-data.sql", "06-os_path_view.sql");
+
+            for (String script : scripts) {
+                ByteArrayResource scriptResource = replaceDollarQuoting(scriptDir.resolve(script));
+                ScriptUtils.executeSqlScript(conn, scriptResource);
+            }
+        }
+    }
+
+    /**
+     * It seems that dollar quoting (used in UDF) is broken in JDBC. Replacing
+     * it with single quotes solves the problem. We replace the quoting here
+     * instead of inside the original files because dollar quoting provides a
+     * better visibility.
+     */
+    private ByteArrayResource replaceDollarQuoting(Path sqlScriptPath) throws Exception {
+
+        String scriptContent = Files.readString(sqlScriptPath);
+
+        if (scriptContent.contains("$func$")) {
+
+            String func = extractFunctionDefinition(scriptContent);
+
+            String originalFunction = "$func$" + func + "$func$";
+            String newFunction = "'" + func.replaceAll("'", "''") + "'";
+
+            scriptContent = scriptContent.replace(originalFunction, newFunction);
+        }
+
+        return new ByteArrayResource(scriptContent.getBytes());
+    }
+
+    private String extractFunctionDefinition(String scriptContent) {
+        Pattern pattern = Pattern.compile("\\$func\\$(.*?)\\$func\\$", Pattern.DOTALL);
+        Matcher matcher = pattern.matcher(scriptContent);
+        if (matcher.find()) {
+            return matcher.group(1);
+        }
+        throw new IllegalArgumentException(scriptContent + " doesn't contain $func$");
+    }
+}
diff --git a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cdc1820b97f57e497a554d677cd376cc0f7b04d4
--- /dev/null
+++ b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
@@ -0,0 +1,33 @@
+package it.inaf.oats.vospace.persistence;
+
+import javax.sql.DataSource;
+import net.ivoa.xml.vospace.v2.ContainerNode;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {DataSourceConfig.class})
+@TestPropertySource(locations = "classpath:test.properties")
+public class NodeDAOTest {
+
+    @Autowired
+    private DataSource dataSource;
+    private NodeDAO dao;
+
+    @BeforeEach
+    public void init() {
+        dao = new NodeDAO(dataSource);
+    }
+
+    @Test
+    public void testListNode() {
+        ContainerNode root = (ContainerNode) dao.listNode("/");
+        assertEquals(1, root.getNodes().size());
+    }
+}
diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c633d739745a86c8eee86db5c5528799e3480dd4
--- /dev/null
+++ b/src/test/resources/test.properties
@@ -0,0 +1 @@
+init_database_scripts_path=../../../vospace-transfer-service/file_catalog