diff --git a/api/go.mod b/api/go.mod
index b131187..8b73d49 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -4,6 +4,7 @@ go 1.13
 
 require (
 	github.com/99designs/gqlgen v0.10.2
+	github.com/fatih/color v1.9.0
 	github.com/go-chi/chi v3.3.2+incompatible
 	github.com/go-chi/cors v1.0.0
 	github.com/go-sql-driver/mysql v1.5.0
@@ -18,6 +19,7 @@ require (
 	github.com/rs/cors v1.6.0
 	github.com/sirupsen/logrus v1.4.2
 	github.com/vektah/gqlparser v1.2.0
+	github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
 	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
 	golang.org/x/image v0.0.0-20200119044424-58c23975cae1
 )
diff --git a/api/go.sum b/api/go.sum
index 9dd7e32..03aa30f 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -6,6 +6,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ=
 github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
 github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
@@ -36,6 +38,11 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
 github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/nf/cr2 v0.0.0-20180623103828-4699471a17ed h1:QP63yO3XEt8tJ1DgBsNjbOyBGjy2eHy9ITK4Eisr9rg=
@@ -67,6 +74,8 @@ github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUd
 github.com/vektah/gqlparser v1.2.0 h1:ntkSCX7F5ZJKl+HIVnmLaO269MruasVpNiMOjX9kgo0=
 github.com/vektah/gqlparser v1.2.0/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
 github.com/viktorstrate/photoview v0.0.0-20200119220544-691e4c7dc433 h1:n6jGnDctC9HI7B1rnc5ATQPYaaxQaJtS8bI6oI0QV34=
+github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
+github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
@@ -74,8 +83,11 @@ golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+o
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw=
diff --git a/api/graphql/notification/Notification.go b/api/graphql/notification/Notification.go
index 6abd309..f436954 100644
--- a/api/graphql/notification/Notification.go
+++ b/api/graphql/notification/Notification.go
@@ -7,7 +7,7 @@ import (
 	"sync"
 )
 
-type NotificationChannel = <-chan *models.Notification
+type NotificationChannel = chan<- *models.Notification
 
 type NotificationListener struct {
 	listenerID int
@@ -44,6 +44,9 @@ func DeregisterListener(listenerID int) error {
 	defer notificationLock.Unlock()
 
 	for i, listener := range notificationListeners {
+
+		log.Println("Deregistering notification listener")
+
 		if listener.listenerID == listenerID {
 
 			if len(notificationListeners) > 1 {
@@ -62,3 +65,16 @@ func DeregisterListener(listenerID int) error {
 
 	return errors.New("ListenerID not found, while trying to deregister it")
 }
+
+func BroadcastNotification(notification *models.Notification) {
+
+	log.Println("Broadcasting notification")
+
+	notificationLock.Lock()
+	defer notificationLock.Unlock()
+
+	for _, listener := range notificationListeners {
+		listener.channel <- notification
+	}
+
+}
diff --git a/api/graphql/resolvers/notification.go b/api/graphql/resolvers/notification.go
index 9c1ec71..ca48a15 100644
--- a/api/graphql/resolvers/notification.go
+++ b/api/graphql/resolvers/notification.go
@@ -4,17 +4,18 @@ import (
 	"context"
 
 	"github.com/viktorstrate/photoview/api/graphql/auth"
+	"github.com/viktorstrate/photoview/api/graphql/models"
 	"github.com/viktorstrate/photoview/api/graphql/notification"
 )
 
-func (r *subscriptionResolver) Notification(ctx context.Context) (notification.NotificationChannel, error) {
+func (r *subscriptionResolver) Notification(ctx context.Context) (<-chan *models.Notification, error) {
 
 	user := auth.UserFromContext(ctx)
 	if user == nil {
 		return nil, auth.ErrUnauthorized
 	}
 
-	notificationChannel := make(notification.NotificationChannel, 1)
+	notificationChannel := make(chan *models.Notification, 1)
 
 	listenerID := notification.RegisterListener(user, notificationChannel)
 
diff --git a/api/scanner/scanner.go b/api/scanner/scanner.go
index 4420344..ce7ad84 100644
--- a/api/scanner/scanner.go
+++ b/api/scanner/scanner.go
@@ -13,6 +13,8 @@ import (
 
 	"github.com/h2non/filetype"
 	"github.com/viktorstrate/photoview/api/graphql/models"
+	"github.com/viktorstrate/photoview/api/graphql/notification"
+	"github.com/viktorstrate/photoview/api/utils"
 )
 
 type scanner_cache map[string]interface{}
@@ -75,6 +77,16 @@ func ScanUser(database *sql.DB, userId int) error {
 }
 
 func scan(database *sql.DB, user *models.User) {
+
+	notifyKey := utils.GenerateToken()
+
+	notification.BroadcastNotification(&models.Notification{
+		Key:     notifyKey,
+		Type:    models.NotificationTypeMessage,
+		Header:  "User scan started",
+		Content: "Scanning has started...",
+	})
+
 	// Start scanning
 	scanner_cache := make(scanner_cache)
 	album_paths_scanned := make([]interface{}, 0)
@@ -179,6 +191,14 @@ func scan(database *sql.DB, user *models.User) {
 
 	cleanupCache(database, album_paths_scanned, user)
 
+	notification.BroadcastNotification(&models.Notification{
+		Key:      notifyKey,
+		Type:     models.NotificationTypeMessage,
+		Header:   "User scan completed",
+		Content:  "Scanning has been completed...",
+		Positive: true,
+	})
+
 	log.Println("Done scanning")
 }
 
diff --git a/api/server.go b/api/server.go
index 2a0723e..6493836 100644
--- a/api/server.go
+++ b/api/server.go
@@ -45,10 +45,9 @@ func main() {
 	}
 
 	rootRouter := mux.NewRouter()
+
 	rootRouter.Use(auth.Middleware(db))
-
-	// router.Use(middleware.Logger)
-
+	rootRouter.Use(server.LoggingMiddleware)
 	rootRouter.Use(server.CORSMiddleware(devMode))
 
 	graphqlResolver := resolvers.Resolver{Database: db}
diff --git a/api/server/logging.go b/api/server/logging.go
new file mode 100644
index 0000000..a19bf09
--- /dev/null
+++ b/api/server/logging.go
@@ -0,0 +1,85 @@
+package server
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
+	"time"
+
+	"github.com/viktorstrate/photoview/api/graphql/auth"
+	"github.com/wsxiaoys/terminal/color"
+)
+
+func LoggingMiddleware(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		start := time.Now()
+
+		statusWriter := newStatusResponseWriter(&w)
+		next.ServeHTTP(statusWriter, r)
+
+		elapsed := time.Since(start)
+		date := time.Now().Format("2006/01/02 15:04:05")
+
+		status := statusWriter.status
+		var statusColor string
+		switch {
+		case status < 200:
+			statusColor = color.Colorize("b")
+		case status < 300:
+			statusColor = color.Colorize("g")
+		case status < 400:
+			statusColor = color.Colorize("c")
+		case status < 500:
+			statusColor = color.Colorize("y")
+		default:
+			statusColor = color.Colorize("r")
+		}
+
+		user := auth.UserFromContext(r.Context())
+		userText := "unauthenticated"
+		if user != nil {
+			userText = color.Sprintf("@ruser: %s", user.Username)
+		}
+
+		statusText := color.Sprintf("%s%s %d", statusColor, r.Method, status)
+		requestText := fmt.Sprintf("%s%s", r.Host, r.URL.Path)
+		durationText := color.Sprintf("@c%s", elapsed)
+
+		fmt.Printf("%s %s %s %s %s\n", date, statusText, requestText, durationText, userText)
+
+	})
+}
+
+type statusResponseWriter struct {
+	http.ResponseWriter
+	status   int
+	hijacker http.Hijacker
+}
+
+func newStatusResponseWriter(w *http.ResponseWriter) *statusResponseWriter {
+	return &statusResponseWriter{
+		ResponseWriter: *w,
+		hijacker:       (*w).(http.Hijacker),
+	}
+}
+
+func (w *statusResponseWriter) WriteHeader(status int) {
+	w.status = status
+	w.ResponseWriter.WriteHeader(status)
+}
+
+func (w *statusResponseWriter) Write(b []byte) (int, error) {
+	if w.status == 0 {
+		w.status = 200
+	}
+	return w.ResponseWriter.Write(b)
+}
+
+func (w *statusResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	if w.hijacker == nil {
+		return nil, nil, errors.New("http.Hijacker not implemented by underlying http.ResponseWriter")
+	}
+	return w.hijacker.Hijack()
+}
diff --git a/ui/src/components/messages/SubscriptionsHook.js b/ui/src/components/messages/SubscriptionsHook.js
index 053eda2..7292372 100644
--- a/ui/src/components/messages/SubscriptionsHook.js
+++ b/ui/src/components/messages/SubscriptionsHook.js
@@ -38,35 +38,58 @@ const SubscriptionsHook = ({ messages, setMessages }) => {
 
     if (!data) return
 
-    const update = data.scannerStatusUpdate
     const newMessages = [...messages]
 
-    if (update.success) {
-      newMessages[0] = {
-        key: 'primary',
-        type: 'progress',
-        props: {
-          header: update.finished ? 'Synced' : 'Syncing',
-          content: update.message,
-          percent: update.progress,
-          positive: update.finished,
-        },
-      }
+    const msg = data.notification
 
-      if (!update.finished) newMessages[0].props.onDismiss = null
-    } else {
-      const key = Math.random().toString(26)
-      newMessages.push({
-        key,
-        type: 'message',
-        props: {
-          header: 'Sync error',
-          content: update.message,
-          negative: true,
-        },
-      })
+    const newNotification = {
+      key: msg.key,
+      type: msg.type.toLowerCase(),
+      props: {
+        header: msg.header,
+        content: msg.content,
+        negative: msg.negative,
+        positive: msg.positive,
+      },
     }
 
+    const notifyIndex = newMessages.findIndex(
+      msg => msg.key == newNotification.key
+    )
+    if (notifyIndex != -1) {
+      newMessages[notifyIndex] = newNotification
+    } else {
+      newMessages.push(newNotification)
+    }
+
+    // const update = data.scannerStatusUpdate
+
+    // if (update.success) {
+    //   newMessages[0] = {
+    //     key: 'primary',
+    //     type: 'progress',
+    //     props: {
+    //       header: update.finished ? 'Synced' : 'Syncing',
+    //       content: update.message,
+    //       percent: update.progress,
+    //       positive: update.finished,
+    //     },
+    //   }
+
+    //   if (!update.finished) newMessages[0].props.onDismiss = null
+    // } else {
+    //   const key = Math.random().toString(26)
+    //   newMessages.push({
+    //     key,
+    //     type: 'message',
+    //     props: {
+    //       header: 'Sync error',
+    //       content: update.message,
+    //       negative: true,
+    //     },
+    //   })
+    // }
+
     setMessages(newMessages)
   }, [data, error])
 
