diff --git a/src/auth.js b/src/auth.js
index 5f6159b19a1da98f9c30da7178d2dfab6d852ec8..31945a591b9ee1e8a79ddde8bfeec7b2619effbf 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -1,6 +1,8 @@
 import axios from 'axios';
 import * as localStorage from './localStorage';
-import { apiUrl } from './config.json';
+import config from './config.json';
+
+const m = require('mithril');
 
 // Object which stores the current login-state
 const APISession = {
@@ -8,8 +10,14 @@ const APISession = {
   token: '',
 };
 
+export function resetSession() {
+  APISession.authenticated = false;
+  APISession.token = '';
+  m.route.set('/login');
+}
+
 const amivapi = axios.create({
-  baseURL: apiUrl,
+  baseURL: config.apiUrl,
   timeout: 10000,
   headers: { 'Content-Type': 'application/json' },
 });
@@ -33,8 +41,8 @@ function checkToken(token) {
 export function checkAuthenticated() {
   // return a promise that resolves always, with a bool that shows whether
   // the user is authenticated
-  return new Promise((resolve, reject) => {
-    if (APISession.authenticated) resolve(true);
+  return new Promise((resolve) => {
+    if (APISession.authenticated) resolve();
     else {
       console.log('looking for token');
       // let's see if we have a stored token
@@ -45,31 +53,27 @@ export function checkAuthenticated() {
         checkToken(token).then(() => {
           APISession.token = token;
           APISession.authenticated = true;
-          resolve(true);
-        }).catch(() => {
-          resolve(false);
-        });
-      } else resolve(false);
+          resolve();
+        }).catch(resetSession);
+      } else resetSession();
     }
   });
 }
 
 export function getSession() {
   // Promise resolves with authenticated axios-session or fails
-  return new Promise((resolve, reject) => {
-    checkAuthenticated().then((authenticated) => {
-      if (authenticated) {
-        const authenticatedSession = axios.create({
-          baseURL: apiUrl,
-          timeout: 10000,
-          headers: {
-            'Content-Type': 'application/json',
-            Authorization: APISession.token,
-          },
-        });
-        resolve(authenticatedSession);
-      } else reject();
-    }).catch(reject);
+  return new Promise((resolve) => {
+    checkAuthenticated().then(() => {
+      const authenticatedSession = axios.create({
+        baseURL: config.apiUrl,
+        timeout: 10000,
+        headers: {
+          'Content-Type': 'application/json',
+          Authorization: APISession.token,
+        },
+      });
+      resolve(authenticatedSession);
+    }).catch(resetSession);
   });
 }
 
@@ -87,3 +91,162 @@ export function login(username, password) {
       }).catch(reject);
   });
 }
+
+export class ResourceHandler {
+  constructor(resource, searchKeys = false) {
+    this.resource = resource;
+    this.searchKeys = searchKeys || config[resource].searchKeys;
+    this.patchKeys = config[resource].patchableKeys;
+
+    checkAuthenticated();
+  }
+
+  // definitions of query parameters in addition to API go here
+  buildQuerystring(query) {
+    const queryKeys = Object.keys(query);
+
+    if (queryKeys.length === 0) return '';
+
+    const fullQuery = {};
+
+    if ('search' in query && query.search.length > 0) {
+      // translate search into where, we just look if any field contains search
+      const searchQuery = {
+        $or: this.searchKeys.map((key) => {
+          const fieldQuery = {};
+          fieldQuery[key] = query.search;
+          return fieldQuery;
+        }),
+      };
+
+      // if there exists already a where-filter, AND them together
+      if ('where' in query) {
+        fullQuery.where = JSON.stringify({ $and: [searchQuery, query.where] });
+      } else {
+        fullQuery.where = JSON.stringify(searchQuery);
+      }
+    } else {
+      fullQuery.where = JSON.stringify(query.where);
+    }
+
+    // add all other keys
+    queryKeys.filter(key => (key !== 'where' && key !== 'search'))
+      .forEach((key) => { fullQuery[key] = JSON.stringify(query[key]); });
+
+    console.log(fullQuery);
+
+    // now we can acutally build the query string
+    return `?${m.buildQueryString(fullQuery)}`;
+  }
+
+  get(query) {
+    return new Promise((resolve, reject) => {
+      getSession().then((api) => {
+        let url = this.resource;
+        if (Object.keys(query).length > 0) url += this.buildQuerystring(query);
+        api.get(url).then((response) => {
+          if (response.status >= 400) {
+            resetSession();
+            reject();
+          } else {
+            resolve(response.data);
+          }
+        }).catch((e) => {
+          console.log(e);
+          reject(e);
+        });
+      });
+    });
+  }
+
+  getItem(id, embedded = {}) {
+    return new Promise((resolve, reject) => {
+      getSession().then((api) => {
+        let url = `${this.resource}/${id}`;
+        // in case embedded is specified, append to url
+        if (Object.keys(embedded).length > 0) {
+          url += `?${m.buildQueryString({
+            embedded: JSON.stringify(this.embedded),
+          })}`;
+        }
+        api.get(url).then((response) => {
+          if (response.status >= 400) {
+            resetSession();
+            reject();
+          } else {
+            resolve(response.data);
+          }
+        }).catch((e) => {
+          console.log(e);
+          reject(e);
+        });
+      });
+    });
+  }
+
+  post(item) {
+    return new Promise((resolve, reject) => {
+      getSession().then((api) => {
+        api.post(this.resource, {
+          data: item,
+        }).then((response) => {
+          if (response.status >= 400) {
+            resetSession();
+            reject();
+          } else {
+            resolve(response.data);
+          }
+        }).catch((e) => {
+          console.log(e);
+          reject(e);
+        });
+      });
+    });
+  }
+
+  patch(item) {
+    return new Promise((resolve, reject) => {
+      getSession().then((api) => {
+        // not all fields in the item can be patched. We filter out the fields
+        // we are allowed to send
+        const submitData = {};
+        this.patchFields.forEach((key) => { submitData[key] = this.data[key]; });
+
+        api.patch(`${this.resource}/${item._id}`, {
+          headers: { 'If-Match': item._etag },
+          data: submitData,
+        }).then((response) => {
+          if (response.status >= 400) {
+            resetSession();
+            reject();
+          } else {
+            resolve(response.data);
+          }
+        }).catch((e) => {
+          console.log(e);
+          reject(e);
+        });
+      });
+    });
+  }
+
+  delete(item) {
+    return new Promise((resolve, reject) => {
+      getSession().then((api) => {
+        api.delete(`${this.resource}/${item._id}`, {
+          headers: { 'If-Match': item._etag },
+        }).then((response) => {
+          if (response.status >= 400) {
+            resetSession();
+            reject();
+          } else {
+            resolve();
+          }
+        }).catch((e) => {
+          console.log(e);
+          reject(e);
+        });
+      });
+    });
+  }
+}
diff --git a/src/config.json b/src/config.json
index 7369fd494bc01d0e35323db11637d18e2b25332e..980c1971aefb30ff4d6ef8a374029d9e6f3f5f8d 100644
--- a/src/config.json
+++ b/src/config.json
@@ -1,6 +1,6 @@
 {
     "apiUrl": "https://amiv-api.ethz.ch/",
-    "Events": {
+    "events": {
         "keyDescriptors": {
             "title_de": "Titel auf Deutsch",
             "title_en": "Title in English",
@@ -41,7 +41,7 @@
             "priority"
         ]
     },
-    "Users": {
+    "users": {
         "keyDescriptors": {
             "legi": "Legi Number",
             "firstname": "First Name",
@@ -60,6 +60,13 @@
             "legi",
             "membership"
         ],
+        "searchKeys": [
+            "firstname",
+            "lastname",
+            "nethz",
+            "legi",
+            "department"
+        ],
         "patchableKeys": [
             "firstname",
             "lastname",
@@ -67,5 +74,18 @@
             "membership",
             "gender"
         ]
+    },
+    "groups": {
+        "keyDescriptors": {
+            "name": "Name"
+        },
+        "searchKeys": ["name"],
+        "patchableKeys": ["name"]
+    },
+    "groupmemberships": {
+        "patchableKeys": ["user", "group"]
+    },
+    "eventsignups": {
+        "patchableKeys": ["event"]
     }
 }
diff --git a/src/eventTool.js b/src/eventTool.js
index f455fda56dbc6622b5653c485461b1fb5f0b1dce..2cdf9fa59ef35f55b9cf7a5e64c1ba79d6b3205b 100644
--- a/src/eventTool.js
+++ b/src/eventTool.js
@@ -2,7 +2,7 @@ import ItemView from './views/itemView';
 import EditView from './views/editView';
 import { inputGroup, selectGroup, submitButton } from './views/elements';
 import TableView from './views/tableView';
-import { Events as config } from './config.json';
+import { events as config } from './config.json';
 
 const m = require('mithril');
 
diff --git a/src/userTool.js b/src/userTool.js
index f2baa5ce0995ba936a9586ed67297a12edf014e5..8e302c33a39529787c05d0caeb235634d505c7d9 100644
--- a/src/userTool.js
+++ b/src/userTool.js
@@ -3,7 +3,7 @@ import EditView from './views/editView';
 import TableView from './views/tableView';
 import { inputGroup, selectGroup, submitButton } from './views/elements';
 import SelectList from './views/selectList';
-import { Users as config } from './config.json';
+import { users as config } from './config.json';
 import { getSession } from './auth';
 
 const m = require('mithril');
@@ -59,6 +59,7 @@ class UserView extends ItemView {
       m(TableView, {
         resource: 'groupmemberships',
         keys: ['group.name', 'expiry'],
+        searchKeys: ['group.name'],
         query: {
           where: { user: this.id },
           embedded: { group: 1 },
diff --git a/src/views/itemView.js b/src/views/itemView.js
index edc23f4fdd3ef474860e51122d7de5f5df5726dc..bed55516635cb9327af137a005e1a894f9aa1d9b 100644
--- a/src/views/itemView.js
+++ b/src/views/itemView.js
@@ -1,4 +1,4 @@
-import { getSession } from '../auth';
+import { ResourceHandler } from '../auth';
 
 const m = require('mithril');
 
@@ -9,46 +9,18 @@ export default class ItemView {
    *  - call constructor with 'resource'
    *  - either make sure m.route.params('id') exists or set this.id in
    *    constructor
-   *
-   *  Provides Methods:
-   *  - loadItemData: Loads data specified by resource and id into this.data
-   *    (is per default called by oninit)
    */
   constructor(resource, embedded) {
     this.data = null;
     this.id = m.route.param('id');
-    this.resource = resource;
-    this.embedded = embedded;
-  }
-
-  loadItemData(session) {
-    let url = `${this.resource}/${this.id}`;
-    if (this.embedded) {
-      url += `?${m.buildQueryString({ embedded: JSON.stringify(this.embedded) })}`;
-      //url += `?embedded=${JSON.stringify(this.embedded)}`;
-    }
-
-    m.request({
-      url: `https://amiv-api.ethz.ch/${url}`,
-      headers: {
-        Authorization: "ZF3D6SxEK1TvmcZ9qEGB/VUTo+8Td3UpyOPZJQ+WzgufoAJpmmirIiUTo84QDdCPtzOUiS47OnoXdpXo1jSGWWACjweLABGinKntKMd8QUQ7ESsYA6F4SQZ3nMr6csAP3EBB1MKKPa12i9lvWCOJlt4SwCkZf6MiExeTXsfNldw8z25bXHkivCaXbdD67mogum19w22rj8dNUdafGA51dp146NVpfhXDNtRFHtsHw0jPVETfbt+mN+0QrgQ0LdI6BdeBQhFPVL2zQuHRR6JmnA1m1dMji5DVFzNCRHDm0l2SZfOqrw9nFtkUegd86KooNrT6xoXrfH7q7jaaeRer7Q=="
-      }
-    }).then((response) => {
-      console.log(response)
-    })
-
-    console.log(url);
-    session.get(url).then((response) => {
-      this.data = response.data;
-      m.redraw();
-    });
+    this.handler = new ResourceHandler(resource);
+    this.embedded = embedded || {};
   }
 
   oninit() {
-    getSession().then((apiSession) => {
-      this.loadItemData(apiSession);
-    }).catch(() => {
-      m.route.set('/login');
+    this.handler.getItem(this.id, this.embedded).then((item) => {
+      this.data = item;
+      m.redraw();
     });
   }
 }
diff --git a/src/views/tableView.js b/src/views/tableView.js
index 99cc1c220812587dd39b927a14ab47fe179a37d0..33c4a00f0a9e38bfb518f27c901f31b9552cd47d 100644
--- a/src/views/tableView.js
+++ b/src/views/tableView.js
@@ -1,4 +1,4 @@
-import { getSession } from '../auth';
+import { ResourceHandler } from '../auth';
 
 const m = require('mithril');
 
@@ -45,69 +45,24 @@ export default class TableView {
       titles,
       resource,
       query = false,
+      searchKeys = false,
       onAdd = () => {},
     },
   }) {
     this.items = [];
     this.showKeys = keys;
     this.titles = titles || keys;
-    this.resource = resource;
+    this.handler = new ResourceHandler(resource, searchKeys);
     // the querystring is either given or will be parsed from the url
     this.query = query || m.route.param();
     this.onAdd = onAdd;
   }
 
-  // definitions of query parameters in addition to API go here
-  buildQuerystring() {
-    const queryKeys = Object.keys(this.query);
-
-    if (queryKeys.length === 0) return '';
-
-    const query = {};
-
-    if ('search' in this.query && this.query.search.length > 0) {
-      // translate search into where, we just look if any field contains search
-      const searchQuery = {
-        $or: this.showKeys.map((key) => {
-          const fieldQuery = {};
-          fieldQuery[key] = this.query.search;
-          return fieldQuery;
-        }),
-      };
-
-      // if there exists already a where-filter, AND them together
-      if ('where' in this.query) {
-        query.where = JSON.stringify({ $and: [searchQuery, this.query.where] });
-      } else {
-        query.where = JSON.stringify(searchQuery);
-      }
-    } else {
-      query.where = JSON.stringify(this.query.where);
-    }
-
-    // add all other keys
-    queryKeys.filter(key => (key !== 'where' && key !== 'search'))
-      .forEach((key) => { query[key] = JSON.stringify(this.query[key]); });
-
-    console.log(query);
-
-    // now we can acutally build the query string
-    return `?${m.buildQueryString(query)}`;
-  }
-
   buildList() {
-    getSession().then((apiSession) => {
-      let url = this.resource;
-      if (Object.keys(this.query).length > 0) url += this.buildQuerystring();
-      apiSession.get(url).then((response) => {
-        this.items = response.data._items;
-        console.log(this.items);
-        m.redraw();
-      }).catch((e) => {
-        console.log(e);
-      });
-    }).catch(() => {
-      m.route.set('/login');
+    this.handler.get(this.query).then((data) => {
+      this.items = data._items;
+      console.log(this.items);
+      m.redraw();
     });
   }