diff --git a/dub.json b/dub.json
index c732017..611ed11 100644
--- a/dub.json
+++ b/dub.json
@@ -5,6 +5,7 @@
 	],
 	"dependencies": {
 		"dauth": "~>0.6.3",
+		"mysql-native": "~>1.1.2",
 		"vibe-d": "~>0.8.1",
 		"poodinis": "~>8.0.1"
 	},
diff --git a/schema.sql b/schema.sql
new file mode 100644
index 0000000..3eae14e
--- /dev/null
+++ b/schema.sql
@@ -0,0 +1,24 @@
+CREATE DATABASE CalendarWebapp;
+USE CalendarWebapp;
+
+CREATE TABLE users (
+  id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  username CHAR(30) NOT NULL UNIQUE,
+  passwordHash CHAR(92) NOT NULL,
+  privilege TINYINT UNSIGNED NOT NULL,
+  PRIMARY KEY (id)
+);
+
+CREATE TABLE events (
+  id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  begin DATE NOT NULL,
+  end DATE,
+  name CHAR(128) NOT NULL,
+  description TEXT NOT NULL,
+  type TINYINT UNSIGNED NOT NULL,
+  shout BOOLEAN NOT NULL,
+  PRIMARY KEY (id)
+);
+
+INSERT INTO users (username, passwordHash, privilege) VALUES ('foo',
+'$5$eGohWUmw9yyiTqG7kti1MmT/jR52raEVlYuHQJa/sYk=$ucI+vTQq38Rr5RUAatd4Om0Dy9fuF0oKgkuVCuXSnxc=', 2);
diff --git a/source/calendarwebapp/authenticator.d b/source/calendarwebapp/authenticator.d
index a15c380..4ab0b5b 100644
--- a/source/calendarwebapp/authenticator.d
+++ b/source/calendarwebapp/authenticator.d
@@ -4,18 +4,20 @@ import calendarwebapp.passhash : PasswordHasher;
 
 import poodinis;
 
+import std.conv : to;
 import std.range : InputRange;
 import std.typecons : nullable, Nullable;
 
 import vibe.data.bson;
+
 import vibe.db.mongo.collection : MongoCollection;
 
 interface Authenticator
 {
     Nullable!AuthInfo checkUser(string username, string password) @safe;
-    void addUser(AuthInfo authInfo) @safe;
-    InputRange!AuthInfo getAllUsers() @safe;
-    void removeUser(BsonObjectID id) @safe;
+    void addUser(AuthInfo authInfo);
+    InputRange!AuthInfo getAllUsers();
+    void removeUser(string id);
 }
 
 class MongoDBAuthenticator(Collection = MongoCollection) : Authenticator
@@ -47,8 +49,17 @@ public:
 
     void addUser(AuthInfo authInfo) @safe
     {
-        if (!authInfo.id.valid)
-            authInfo.id = BsonObjectID.generate;
+        import std.conv : ConvException;
+
+        try
+        {
+            if (!BsonObjectID.fromString(authInfo.id).valid)
+                throw new ConvException("invalid BsonObjectID.");
+        }
+        catch (ConvException)
+        {
+            authInfo.id = BsonObjectID.generate.to!string;
+        }
 
         users.insert(authInfo.serializeToBson);
     }
@@ -61,7 +72,7 @@ public:
         return users.find().map!(deserializeBson!AuthInfo).inputRangeObject;
     }
 
-    void removeUser(BsonObjectID id) @safe
+    void removeUser(string id) @safe
     {
         users.remove(["_id" : id]);
     }
@@ -74,11 +85,91 @@ enum Privilege
     Admin
 }
 
+class MySQLAuthenticator : Authenticator
+{
+private:
+    import mysql;
+
+    @Autowire MySQLPool pool;
+    @Autowire PasswordHasher passwordHasher;
+
+public:
+    Nullable!AuthInfo checkUser(string username, string password) @trusted
+    {
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close();
+        auto prepared = cn.prepare(
+                "SELECT id, username, passwordHash, privilege FROM users WHERE username = ?");
+        prepared.setArg(0, username);
+        auto result = prepared.query();
+        /* checkHash should be called using vibe.core.concurrency.async to
+           avoid blocking, but https://github.com/vibe-d/vibe.d/issues/1521 is
+           blocking this */
+        if (!result.empty)
+        {
+            auto authInfo = toAuthInfo(result.front);
+            if (passwordHasher.checkHash(password, authInfo.passwordHash))
+            {
+                return authInfo.nullable;
+            }
+        }
+        return Nullable!AuthInfo.init;
+    }
+
+    void addUser(AuthInfo authInfo)
+    {
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close;
+        auto prepared = cn.prepare(
+                "INSERT INTO users (username, passwordHash, privilege) VALUES(?, ?, ?)");
+        prepared.setArgs(authInfo.username, authInfo.passwordHash, authInfo.privilege.to!uint);
+        prepared.exec();
+    }
+
+    InputRange!AuthInfo getAllUsers()
+    {
+        import std.algorithm : map;
+        import std.range : inputRangeObject;
+
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close;
+        auto prepared = cn.prepare("SELECT id, username, passwordHash, privilege FROM users");
+        return prepared.querySet.map!(r => toAuthInfo(r)).inputRangeObject;
+    }
+
+    void removeUser(string id)
+    {
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close;
+        auto prepared = cn.prepare("DELETE FROM users WHERE id = ?");
+        prepared.setArg(0, id.to!uint);
+        prepared.exec();
+    }
+
+private:
+
+    AuthInfo toAuthInfo(Row r)
+    {
+        import std.conv : to;
+
+        AuthInfo authInfo;
+        authInfo.id = r[0].get!uint.to!string;
+        authInfo.username = r[1].get!string;
+        authInfo.passwordHash = r[2].get!string;
+        authInfo.privilege = r[3].get!uint.to!Privilege;
+        return authInfo;
+    }
+}
+
 struct AuthInfo
 {
     import vibe.data.serialization : name;
 
-    @name("_id") BsonObjectID id;
+    @name("_id") string id;
     string username;
     string passwordHash;
     Privilege privilege;
diff --git a/source/calendarwebapp/calendarwebapp.d b/source/calendarwebapp/calendarwebapp.d
index 5757bfb..fdc958f 100644
--- a/source/calendarwebapp/calendarwebapp.d
+++ b/source/calendarwebapp/calendarwebapp.d
@@ -65,23 +65,23 @@ public:
         render!("createevent.dt", _error, authInfo);
     }
 
+
     @auth(Role.user | Role.admin) @errorDisplay!getCreateevent void postCreateevent(Date begin,
-            Nullable!Date end, string description, string name, EventType type, bool shout) @safe
+            Nullable!Date end, string description, string name, EventType type, bool shout)
     {
         import std.array : replace, split;
 
         if (!end.isNull)
             enforce(end - begin >= 1.days,
                     "Mehrtägige Ereignisse müssen mindestens einen Tag dauern");
-        auto event = Event(BsonObjectID.generate, begin, end, name,
-                description.replace("\r", ""), type, shout);
+        auto event = Event("", begin, end, name, description.replace("\r", ""), type, shout);
 
         eventStore.addEvent(event);
 
         redirect("/");
     }
 
-    @auth(Role.user | Role.admin) void postRemoveevent(BsonObjectID id) @safe
+    @auth(Role.user | Role.admin) void postRemoveevent(string id)
     {
         eventStore.removeEvent(id);
         redirect("/");
@@ -94,7 +94,7 @@ public:
         render!("showusers.dt", users, authInfo);
     }
 
-    @auth(Role.admin) void postRemoveuser(BsonObjectID id) @safe
+    @auth(Role.admin) void postRemoveuser(string id)
     {
         authenticator.removeUser(id);
         redirect("/users");
@@ -107,9 +107,9 @@ public:
     }
 
     @auth(Role.admin) @errorDisplay!getCreateuser void postCreateuser(string username,
-            string password, Privilege role) @safe
+            string password, Privilege role)
     {
-        authenticator.addUser(AuthInfo(BsonObjectID.generate, username,
+        authenticator.addUser(AuthInfo("", username,
                 passwordHasher.generateHash(password), role));
         redirect("/users");
     }
@@ -121,7 +121,7 @@ private:
         string field;
     }
 
-    SessionVar!(AuthInfo, "authInfo") authInfo = AuthInfo(BsonObjectID.init,
+    SessionVar!(AuthInfo, "authInfo") authInfo = AuthInfo("",
             string.init, string.init, Privilege.None);
 
     @Autowire EventStore eventStore;
diff --git a/source/calendarwebapp/configuration.d b/source/calendarwebapp/configuration.d
index 7a5ec87..ffb162d 100644
--- a/source/calendarwebapp/configuration.d
+++ b/source/calendarwebapp/configuration.d
@@ -1,10 +1,13 @@
 module calendarwebapp.configuration;
 
-import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator;
+import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator,
+    MySQLAuthenticator;
 import calendarwebapp.calendarwebapp : CalendarWebapp;
-import calendarwebapp.event : EventStore, MongoDBEventStore;
+import calendarwebapp.event : EventStore, MongoDBEventStore, MySQLEventStore;
 import calendarwebapp.passhash : PasswordHasher, SHA256PasswordHasher;
 
+import mysql : MySQLPool;
+
 import poodinis;
 
 import vibe.db.mongo.client : MongoClient;
@@ -17,9 +20,11 @@ public:
     override void registerDependencies(shared(DependencyContainer) container)
     {
         auto mongoClient = connectMongoDB("localhost");
+        auto pool = new MySQLPool("localhost", "root", "Ilemm3Kzj", "CalendarWebapp");
+        container.register!MySQLPool.existingInstance(pool);
         container.register!MongoClient.existingInstance(mongoClient);
-        container.register!(EventStore, MongoDBEventStore!());
-        container.register!(Authenticator, MongoDBAuthenticator!());
+        container.register!(EventStore, MySQLEventStore);
+        container.register!(Authenticator, MySQLAuthenticator);
         container.register!(PasswordHasher, SHA256PasswordHasher);
         container.register!CalendarWebapp;
         container.register!(ValueInjector!string, StringInjector);
diff --git a/source/calendarwebapp/event.d b/source/calendarwebapp/event.d
index e03ee43..bde695d 100644
--- a/source/calendarwebapp/event.d
+++ b/source/calendarwebapp/event.d
@@ -3,6 +3,7 @@ module calendarwebapp.event;
 import poodinis;
 
 import std.algorithm : map;
+import std.conv : to;
 import std.datetime : Date;
 import std.range.interfaces : InputRange, inputRangeObject;
 import std.typecons : Nullable;
@@ -13,17 +14,17 @@ import vibe.db.mongo.collection : MongoCollection;
 
 interface EventStore
 {
-    Event getEvent(BsonObjectID id) @safe;
-    InputRange!Event getAllEvents() @safe;
-    void addEvent(Event) @safe;
-    InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe;
-    void removeEvent(BsonObjectID id) @safe;
+    Event getEvent(string id);
+    InputRange!Event getAllEvents();
+    void addEvent(Event);
+    /* InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe; */
+    void removeEvent(string id);
 }
 
 class MongoDBEventStore(Collection = MongoCollection) : EventStore
 {
 public:
-    Event getEvent(BsonObjectID id) @safe
+    Event getEvent(string id) @safe
     {
         return events.findOne(["_id" : id]).deserializeBson!Event;
     }
@@ -35,20 +36,29 @@ public:
 
     void addEvent(Event event) @safe
     {
-        if (!event.id.valid)
-            event.id = BsonObjectID.generate;
+        import std.conv : ConvException;
+
+        try
+        {
+            if (!BsonObjectID.fromString(event.id).valid)
+                throw new ConvException("invalid BsonObjectID.");
+        }
+        catch (ConvException)
+        {
+            event.id = BsonObjectID.generate.to!string;
+        }
 
         events.insert(event.serializeToBson);
     }
 
     InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe
     {
-        return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], ["date"
-                    : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event)
+        return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]],
+                ["date" : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event)
             .inputRangeObject;
     }
 
-    void removeEvent(BsonObjectID id) @safe
+    void removeEvent(string id) @safe
     {
         events.remove(["_id" : id]);
     }
@@ -58,6 +68,80 @@ private:
     Collection events;
 }
 
+class MySQLEventStore : EventStore
+{
+private:
+    import mysql;
+
+public:
+    Event getEvent(string id)
+    {
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close;
+        auto prepared = cn.prepare(
+                "SELECT id begin end name description type shout FROM events WHERE id = ?");
+        prepared.setArg(0, id.to!uint);
+        return toEvent(prepared.query.front);
+    }
+
+    InputRange!Event getAllEvents()
+    {
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close;
+        auto prepared = cn.prepare(
+                "SELECT id, begin, end, name, description, type, shout FROM events");
+        return prepared.querySet.map!(r => toEvent(r)).inputRangeObject;
+    }
+
+    void addEvent(Event event)
+    {
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close;
+        auto prepared = cn.prepare(
+                "INSERT INTO events (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)");
+        prepared.setArgs(event.begin, event.end, event.name, event.description,
+                event.type.to!uint, event.shout);
+        prepared.exec();
+    }
+
+    /*     InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe
+    {
+        return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]],
+                ["date" : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event)
+            .inputRangeObject;
+    } */
+
+    void removeEvent(string id)
+    {
+        auto cn = pool.lockConnection();
+        scope (exit)
+            cn.close;
+        auto prepared = cn.prepare("DELETE FROM events WHERE id = ?");
+        prepared.setArg(0, id.to!uint);
+        prepared.exec();
+    }
+
+private:
+    @Autowire MySQLPool pool;
+
+    Event toEvent(Row r)
+    {
+        Event event;
+        event.id = r[0].get!uint.to!string;
+        event.begin = r[1].get!Date;
+        if (r[2].hasValue)
+            event.end = r[2].get!Date;
+        event.name = r[3].get!string;
+        event.description = r[4].get!string;
+        event.type = r[5].get!uint.to!EventType;
+        event.shout = r[6].get!byte.to!bool;
+        return event;
+    }
+}
+
 enum EventType
 {
     Holiday,
@@ -69,7 +153,7 @@ enum EventType
 
 struct Event
 {
-    @serializationName("_id") BsonObjectID id;
+    @serializationName("_id") string id;
     @serializationName("date") Date begin;
     @serializationName("end_date") Nullable!Date end;
     string name;
diff --git a/test/calendarwebapp/testauthenticator.d b/test/calendarwebapp/testauthenticator.d
index c1450be..4276e42 100644
--- a/test/calendarwebapp/testauthenticator.d
+++ b/test/calendarwebapp/testauthenticator.d
@@ -15,7 +15,7 @@ interface Collection
     Bson[] find() @safe;
     Bson findOne(string[string] query) @safe;
     void insert(Bson document) @safe;
-    void remove(BsonObjectID[string] selector) @safe;
+    void remove(string[string] selector) @safe;
 }
 
 class CollectionInjector : ValueInjector!Collection
@@ -46,10 +46,8 @@ public:
             RegistrationOption.doNotAddConcreteTypeRegistration);
     container.register!(PasswordHasher, StubPasswordHasher);
 
-    auto userBson = Bson(["_id" : Bson(BsonObjectID.fromString("5988ef4ae6c19089a1a53b79")),
-            "username" : Bson("foo"), "passwordHash"
-            : Bson("bar"),
-            "privilege" : Bson(1)]);
+    auto userBson = Bson(["_id" : Bson("5988ef4ae6c19089a1a53b79"), "username"
+            : Bson("foo"), "passwordHash" : Bson("bar"), "privilege" : Bson(1)]);
 
     collection.returnValue!"findOne"(Bson(null), userBson, userBson);
 
diff --git a/test/calendarwebapp/testevent.d b/test/calendarwebapp/testevent.d
index e351881..4f0024b 100644
--- a/test/calendarwebapp/testevent.d
+++ b/test/calendarwebapp/testevent.d
@@ -10,15 +10,15 @@ import std.algorithm : map;
 import unit_threaded.mock;
 import unit_threaded.should;
 
-import vibe.data.bson : Bson, BsonObjectID, serializeToBson;
+import vibe.data.bson : Bson, serializeToBson;
 
 interface Collection
 {
-    Bson findOne(BsonObjectID[string] query) @safe;
+    Bson findOne(string[string] query) @safe;
     Bson[] find() @safe;
     Bson[] find(Bson[string][string][][string] query) @safe;
     void insert(Bson document) @safe;
-    void remove(BsonObjectID[string] selector) @safe;
+    void remove(string[string] selector) @safe;
 }
 
 class CollectionInjector : ValueInjector!Collection
@@ -50,7 +50,7 @@ public:
 
     collection.returnValue!"findOne"(Bson(null));
 
-    auto id = BsonObjectID.fromString("599090de97355141140fc698");
+    auto id = "599090de97355141140fc698";
     collection.expect!"findOne"(["_id" : id]);
 
     auto eventStore = container.resolve!(EventStore);
@@ -68,7 +68,7 @@ public:
     container.register!(EventStore, MongoDBEventStore!(Collection))(
             RegistrationOption.doNotAddConcreteTypeRegistration);
 
-    auto id = BsonObjectID.fromString("599090de97355141140fc698");
+    auto id = "599090de97355141140fc698";
     Event event;
     event.id = id;
 
@@ -91,7 +91,7 @@ public:
     container.register!(EventStore, MongoDBEventStore!(Collection))(
             RegistrationOption.doNotAddConcreteTypeRegistration);
 
-    auto id = BsonObjectID.fromString("599090de97355141140fc698");
+    auto id = "599090de97355141140fc698";
     Event event;
     event.id = id;
     auto serializedEvent = event.serializeToBson;
@@ -121,7 +121,7 @@ public:
     container.register!(EventStore, MongoDBEventStore!(Collection))(
             RegistrationOption.doNotAddConcreteTypeRegistration);
 
-    auto id = BsonObjectID.fromString("599090de97355141140fc698");
+    auto id = "599090de97355141140fc698";
     Event event;
     event.id = id;
 
@@ -151,8 +151,7 @@ public:
             RegistrationOption.doNotAddConcreteTypeRegistration);
 
     immutable ids = [
-        BsonObjectID.fromString("599090de97355141140fc698"), BsonObjectID.fromString("599090de97355141140fc698"),
-        BsonObjectID.fromString("59cb9ad8fc0ba5751c0df02b")
+        "599090de97355141140fc698", "599090de97355141140fc698", "59cb9ad8fc0ba5751c0df02b"
     ];
     auto events = ids.map!(id => Event(id)).array;