diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs
index 4fad9551f0437ea6a4eac3951b630929500bc1e3..069a4ec4e201628bac2f86aaf786f6ccb67dec23 100644
--- a/src/database/entities/server.rs
+++ b/src/database/entities/server.rs
@@ -30,7 +30,7 @@ pub struct Member {
 pub struct Ban {
     #[serde(rename = "_id")]
     pub id: MemberCompositeKey,
-    pub reason: String,
+    pub reason: Option<String>,
 }
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -234,6 +234,23 @@ impl Server {
     }
 
     pub async fn join_member(&self, id: &str) -> Result<()> {
+        if get_collection("server_bans")
+            .find_one(
+                doc! {
+                    "_id.server": &self.id,
+                    "_id.user": &id
+                },
+                None
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "find_one",
+                with: "server_bans",
+            })?
+            .is_some() {
+            return Err(Error::Banned)
+        }
+
         get_collection("server_members")
             .insert_one(
                 doc! {
@@ -260,7 +277,7 @@ impl Server {
     }
 
     pub async fn remove_member(&self, id: &str) -> Result<()> {
-        get_collection("server_members")
+        let result = get_collection("server_members")
             .delete_one(
                 doc! {
                     "_id": {
@@ -276,11 +293,13 @@ impl Server {
                 with: "server_members",
             })?;
         
-        ClientboundNotification::ServerMemberLeave {
-            id: self.id.clone(),
-            user: id.to_string()
+        if result.deleted_count > 0 {
+            ClientboundNotification::ServerMemberLeave {
+                id: self.id.clone(),
+                user: id.to_string()
+            }
+            .publish(self.id.clone());
         }
-        .publish(self.id.clone());
 
         Ok(())
     }
diff --git a/src/database/guards/reference.rs b/src/database/guards/reference.rs
index 38a2be05123a2944b21ffe32532b6c5b450424e9..3fe6e06f135f16ae74bfb6373850dbda3cd8fd22 100644
--- a/src/database/guards/reference.rs
+++ b/src/database/guards/reference.rs
@@ -25,7 +25,7 @@ impl Ref {
         Ok(r)
     }
 
-    pub async fn fetch<T: DeserializeOwned>(&self, collection: &'static str) -> Result<T> {
+    async fn fetch<T: DeserializeOwned>(&self, collection: &'static str) -> Result<T> {
         let doc = get_collection(&collection)
             .find_one(
                 doc! {
@@ -84,6 +84,28 @@ impl Ref {
         })?)
     }
 
+    pub async fn fetch_ban(&self, server: &str) -> Result<Ban> {
+        let doc = get_collection("server_bans")
+            .find_one(
+                doc! {
+                    "_id.user": &self.id,
+                    "_id.server": server
+                },
+                None,
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "find_one",
+                with: "server_ban",
+            })?
+            .ok_or_else(|| Error::NotFound)?;
+
+        Ok(from_document::<Ban>(doc).map_err(|_| Error::DatabaseError {
+            operation: "from_document",
+            with: "server_ban",
+        })?)
+    }
+
     pub async fn fetch_message(&self, channel: &Channel) -> Result<Message> {
         let message: Message = self.fetch("messages").await?;
         if &message.channel != channel.id() {
diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs
index 276e2483a3c3c0df09d0f1ad3345cbd3b2429464..c4af2dda57ac28a115e95f3c12ace61c4fc2af10 100644
--- a/src/database/permissions/server.rs
+++ b/src/database/permissions/server.rs
@@ -12,7 +12,9 @@ pub enum ServerPermission {
     ManageMembers   = 0b00000000000000000000000000000010, // 2
     ManageChannels  = 0b00000000000000000000000000000100, // 4
     ManageServer    = 0b00000000000000000000000000001000, // 8
-    // 8 bits of space
+    KickMembers     = 0b00000000000000000000000000010000, // 16
+    BanMembers      = 0b00000000000000000000000000100000, // 32
+    // 6 bits of space
     ChangeNickname  = 0b00000000000000000001000000000000, // 4096
     ManageNicknames = 0b00000000000000000010000000000000, // 8192
     ChangeAvatar    = 0b00000000000000000100000000000000, // 16392
@@ -30,6 +32,8 @@ bitfield! {
     pub get_manage_members, _: 30;
     pub get_manage_channels, _: 29;
     pub get_manage_server, _: 28;
+    pub get_kick_members, _: 27;
+    pub get_ban_members, _: 26;
 
     pub get_change_nickname, _: 19;
     pub get_manage_nicknames, _: 18;
diff --git a/src/routes/servers/ban_create.rs b/src/routes/servers/ban_create.rs
new file mode 100644
index 0000000000000000000000000000000000000000..76a7428fc41a44c333e5d77b6296dbdab2b19c7f
--- /dev/null
+++ b/src/routes/servers/ban_create.rs
@@ -0,0 +1,56 @@
+use crate::database::*;
+use crate::util::result::{Error, Result};
+
+use mongodb::bson::doc;
+use validator::Validate;
+use rocket_contrib::json::Json;
+use serde::{Serialize, Deserialize};
+
+#[derive(Validate, Serialize, Deserialize)]
+pub struct Data {
+    #[validate(length(min = 1, max = 1024))]
+    reason: Option<String>,
+}
+
+#[put("/<server>/bans/<target>", data = "<data>")]
+pub async fn req(user: User, server: Ref, target: Ref, data: Json<Data>) -> Result<()> {
+    let data = data.into_inner();
+    data.validate()
+        .map_err(|error| Error::FailedValidation { error })?;
+    
+    let server = server.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&server)
+        .for_server()
+        .await?;
+    
+    if !perm.get_ban_members() {
+        Err(Error::MissingPermission)?
+    }
+
+    let target = target.fetch_user().await?;
+    let mut document = doc! {
+        "_id": {
+            "server": &server.id,
+            "user": &target.id
+        }
+    };
+
+    if let Some(reason) = data.reason {
+        document.insert("reason", reason);
+    }
+
+    get_collection("server_bans")
+        .insert_one(
+            document,
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "insert_one",
+            with: "server_ban"
+        })?;
+    
+    server.remove_member(&target.id).await
+}
diff --git a/src/routes/servers/ban_list.rs b/src/routes/servers/ban_list.rs
new file mode 100644
index 0000000000000000000000000000000000000000..56899aa40919af7037d9403db6a4426b15ec450d
--- /dev/null
+++ b/src/routes/servers/ban_list.rs
@@ -0,0 +1,44 @@
+use crate::database::*;
+use crate::util::result::{Error, Result};
+
+use futures::StreamExt;
+use rocket_contrib::json::JsonValue;
+use mongodb::bson::{doc, from_document};
+
+#[get("/<target>/bans")]
+pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
+    let target = target.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&target)
+        .for_server()
+        .await?;
+    
+    if !perm.get_manage_members() {
+        Err(Error::MissingPermission)?
+    }
+
+    let mut cursor = get_collection("server_bans")
+        .find(
+            doc! {
+                "_id.server": target.id
+            },
+            None,
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "find",
+            with: "server_bans",
+        })?;
+    
+    let mut bans = vec![];
+    while let Some(result) = cursor.next().await {
+        if let Ok(doc) = result {
+            if let Ok(ban) = from_document::<Ban>(doc) {
+                bans.push(ban);
+            }
+        }
+    }
+    
+    Ok(json!(bans))
+}
diff --git a/src/routes/servers/ban_remove.rs b/src/routes/servers/ban_remove.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ef27a50af3a223935b6c76c4eefde8cb505bc833
--- /dev/null
+++ b/src/routes/servers/ban_remove.rs
@@ -0,0 +1,35 @@
+use crate::database::*;
+use crate::util::result::{Error, Result};
+
+use mongodb::bson::doc;
+
+#[delete("/<server>/bans/<target>")]
+pub async fn req(user: User, server: Ref, target: Ref) -> Result<()> {    
+    let server = server.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&server)
+        .for_server()
+        .await?;
+    
+    if !perm.get_ban_members() {
+        Err(Error::MissingPermission)?
+    }
+
+    let target = target.fetch_ban(&server.id).await?;
+    get_collection("server_bans")
+        .delete_one(
+            doc! {
+                "_id.server": &server.id,
+                "_id.user": &target.id.user
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "delete_one",
+            with: "server_ban"
+        })?;
+    
+    Ok(())
+}
diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs
index cf867c56fa86709347da5946fcf3f3c630a39e92..26638b49f0a3a39db1bafff5478c0b2ccfb7b128 100644
--- a/src/routes/servers/mod.rs
+++ b/src/routes/servers/mod.rs
@@ -12,6 +12,10 @@ mod member_remove;
 mod member_fetch;
 mod member_edit;
 
+mod ban_create;
+mod ban_remove;
+mod ban_list;
+
 mod invites_fetch;
 
 pub fn routes() -> Vec<Route> {
@@ -25,6 +29,9 @@ pub fn routes() -> Vec<Route> {
         member_remove::req,
         member_fetch::req,
         member_edit::req,
+        ban_create::req,
+        ban_remove::req,
+        ban_list::req,
         invites_fetch::req
     ]
 }
diff --git a/src/util/result.rs b/src/util/result.rs
index 44d4bad66ec8ceebdb7cf61aa011c1e0c92d147d..b3225a0df819cc888cc1aa56bd5b87d1cb7690c8 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -38,6 +38,7 @@ pub enum Error {
 
     // ? Server related errors.
     UnknownServer,
+    Banned,
     
     // ? General errors.
     TooManyIds,
@@ -87,6 +88,7 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::NotInGroup => Status::NotFound,
 
             Error::UnknownServer => Status::NotFound,
+            Error::Banned => Status::Forbidden,
 
             Error::FailedValidation { .. } => Status::UnprocessableEntity,
             Error::DatabaseError { .. } => Status::InternalServerError,