diff --git a/server/api/controller/category.go b/api/category.go
similarity index 77%
rename from server/api/controller/category.go
rename to api/category.go
index d7b2922be0503ae1ef8ccadedeefd796c4d9f6ac..67c7e5230d1fef8f0ad5e57a02e660347318cc28 100644
--- a/server/api/controller/category.go
+++ b/api/category.go
@@ -7,14 +7,13 @@ package api
 import (
 	"errors"
 
-	"github.com/miniflux/miniflux/server/api/payload"
-	"github.com/miniflux/miniflux/server/core"
+	"github.com/miniflux/miniflux/http/handler"
 )
 
 // CreateCategory is the API handler to create a new category.
-func (c *Controller) CreateCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) CreateCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
-	category, err := payload.DecodeCategoryPayload(request.Body())
+	category, err := decodeCategoryPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
@@ -41,14 +40,14 @@ func (c *Controller) CreateCategory(ctx *core.Context, request *core.Request, re
 }
 
 // UpdateCategory is the API handler to update a category.
-func (c *Controller) UpdateCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	categoryID, err := request.IntegerParam("categoryID")
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
 	}
 
-	category, err := payload.DecodeCategoryPayload(request.Body())
+	category, err := decodeCategoryPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
@@ -71,7 +70,7 @@ func (c *Controller) UpdateCategory(ctx *core.Context, request *core.Request, re
 }
 
 // GetCategories is the API handler to get a list of categories for a given user.
-func (c *Controller) GetCategories(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) GetCategories(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	categories, err := c.store.Categories(ctx.UserID())
 	if err != nil {
 		response.JSON().ServerError(errors.New("Unable to fetch categories"))
@@ -82,7 +81,7 @@ func (c *Controller) GetCategories(ctx *core.Context, request *core.Request, res
 }
 
 // RemoveCategory is the API handler to remove a category.
-func (c *Controller) RemoveCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RemoveCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	categoryID, err := request.IntegerParam("categoryID")
 	if err != nil {
diff --git a/server/api/controller/controller.go b/api/controller.go
similarity index 100%
rename from server/api/controller/controller.go
rename to api/controller.go
diff --git a/server/api/controller/entry.go b/api/entry.go
similarity index 84%
rename from server/api/controller/entry.go
rename to api/entry.go
index 9c86a7a55c0d0684fd5fd18f4d5699d656c9f8fb..4152da8824406e788c7208575b78ea7f1bd973e9 100644
--- a/server/api/controller/entry.go
+++ b/api/entry.go
@@ -7,13 +7,12 @@ package api
 import (
 	"errors"
 
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/api/payload"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // GetFeedEntry is the API handler to get a single feed entry.
-func (c *Controller) GetFeedEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) GetFeedEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
@@ -46,7 +45,7 @@ func (c *Controller) GetFeedEntry(ctx *core.Context, request *core.Request, resp
 }
 
 // GetEntry is the API handler to get a single entry.
-func (c *Controller) GetEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) GetEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	entryID, err := request.IntegerParam("entryID")
 	if err != nil {
@@ -72,7 +71,7 @@ func (c *Controller) GetEntry(ctx *core.Context, request *core.Request, response
 }
 
 // GetFeedEntries is the API handler to get all feed entries.
-func (c *Controller) GetFeedEntries(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) GetFeedEntries(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
@@ -127,11 +126,11 @@ func (c *Controller) GetFeedEntries(ctx *core.Context, request *core.Request, re
 		return
 	}
 
-	response.JSON().Standard(&payload.EntriesResponse{Total: count, Entries: entries})
+	response.JSON().Standard(&entriesResponse{Total: count, Entries: entries})
 }
 
 // GetEntries is the API handler to fetch entries.
-func (c *Controller) GetEntries(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) GetEntries(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 
 	status := request.QueryStringParam("status", "")
@@ -180,14 +179,14 @@ func (c *Controller) GetEntries(ctx *core.Context, request *core.Request, respon
 		return
 	}
 
-	response.JSON().Standard(&payload.EntriesResponse{Total: count, Entries: entries})
+	response.JSON().Standard(&entriesResponse{Total: count, Entries: entries})
 }
 
 // SetEntryStatus is the API handler to change the status of entries.
-func (c *Controller) SetEntryStatus(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) SetEntryStatus(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 
-	entryIDs, status, err := payload.DecodeEntryStatusPayload(request.Body())
+	entryIDs, status, err := decodeEntryStatusPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(errors.New("Invalid JSON payload"))
 		return
@@ -207,7 +206,7 @@ func (c *Controller) SetEntryStatus(ctx *core.Context, request *core.Request, re
 }
 
 // ToggleBookmark is the API handler to toggle bookmark status.
-func (c *Controller) ToggleBookmark(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ToggleBookmark(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	entryID, err := request.IntegerParam("entryID")
 	if err != nil {
diff --git a/server/api/controller/feed.go b/api/feed.go
similarity index 82%
rename from server/api/controller/feed.go
rename to api/feed.go
index fcaeee780ed73cd3b0cd3181bfebe0f3241f081d..c3fc048fc3d8843569547c879404dcc33dc3fdcd 100644
--- a/server/api/controller/feed.go
+++ b/api/feed.go
@@ -7,14 +7,13 @@ package api
 import (
 	"errors"
 
-	"github.com/miniflux/miniflux/server/api/payload"
-	"github.com/miniflux/miniflux/server/core"
+	"github.com/miniflux/miniflux/http/handler"
 )
 
 // CreateFeed is the API handler to create a new feed.
-func (c *Controller) CreateFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) CreateFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
-	feedURL, categoryID, err := payload.DecodeFeedCreationPayload(request.Body())
+	feedURL, categoryID, err := decodeFeedCreationPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
@@ -54,7 +53,7 @@ func (c *Controller) CreateFeed(ctx *core.Context, request *core.Request, respon
 }
 
 // RefreshFeed is the API handler to refresh a feed.
-func (c *Controller) RefreshFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RefreshFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
@@ -77,7 +76,7 @@ func (c *Controller) RefreshFeed(ctx *core.Context, request *core.Request, respo
 }
 
 // UpdateFeed is the API handler that is used to update a feed.
-func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
@@ -85,7 +84,7 @@ func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, respon
 		return
 	}
 
-	newFeed, err := payload.DecodeFeedModificationPayload(request.Body())
+	newFeed, err := decodeFeedModificationPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
@@ -123,7 +122,7 @@ func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, respon
 }
 
 // GetFeeds is the API handler that get all feeds that belongs to the given user.
-func (c *Controller) GetFeeds(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) GetFeeds(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	feeds, err := c.store.Feeds(ctx.UserID())
 	if err != nil {
 		response.JSON().ServerError(errors.New("Unable to fetch feeds from the database"))
@@ -134,7 +133,7 @@ func (c *Controller) GetFeeds(ctx *core.Context, request *core.Request, response
 }
 
 // GetFeed is the API handler to get a feed.
-func (c *Controller) GetFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) GetFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
@@ -157,7 +156,7 @@ func (c *Controller) GetFeed(ctx *core.Context, request *core.Request, response
 }
 
 // RemoveFeed is the API handler to remove a feed.
-func (c *Controller) RemoveFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RemoveFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
diff --git a/server/api/controller/icon.go b/api/icon.go
similarity index 77%
rename from server/api/controller/icon.go
rename to api/icon.go
index b8e7a61e7a62483d81cf7937af317d74f7eb5d57..7734dbf84834da178c2b7e514dbb5a466b9898f8 100644
--- a/server/api/controller/icon.go
+++ b/api/icon.go
@@ -7,12 +7,11 @@ package api
 import (
 	"errors"
 
-	"github.com/miniflux/miniflux/server/api/payload"
-	"github.com/miniflux/miniflux/server/core"
+	"github.com/miniflux/miniflux/http/handler"
 )
 
 // FeedIcon returns a feed icon.
-func (c *Controller) FeedIcon(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) FeedIcon(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
@@ -36,7 +35,7 @@ func (c *Controller) FeedIcon(ctx *core.Context, request *core.Request, response
 		return
 	}
 
-	response.JSON().Standard(&payload.FeedIcon{
+	response.JSON().Standard(&feedIcon{
 		ID:       icon.ID,
 		MimeType: icon.MimeType,
 		Data:     icon.DataURL(),
diff --git a/server/api/payload/payload.go b/api/payload.go
similarity index 67%
rename from server/api/payload/payload.go
rename to api/payload.go
index 25cd65782a533719c5701a67f32eb2d66bcd699a..46c3b044f72b27a650cbb42979e08443fe03d1dc 100644
--- a/server/api/payload/payload.go
+++ b/api/payload.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package payload
+package api
 
 import (
 	"encoding/json"
@@ -12,21 +12,18 @@ import (
 	"github.com/miniflux/miniflux/model"
 )
 
-// FeedIcon represents the feed icon response.
-type FeedIcon struct {
+type feedIcon struct {
 	ID       int64  `json:"id"`
 	MimeType string `json:"mime_type"`
 	Data     string `json:"data"`
 }
 
-// EntriesResponse represents the response sent when fetching entries.
-type EntriesResponse struct {
+type entriesResponse struct {
 	Total   int           `json:"total"`
 	Entries model.Entries `json:"entries"`
 }
 
-// DecodeUserPayload unserialize JSON user object.
-func DecodeUserPayload(data io.Reader) (*model.User, error) {
+func decodeUserPayload(data io.Reader) (*model.User, error) {
 	var user model.User
 
 	decoder := json.NewDecoder(data)
@@ -37,8 +34,7 @@ func DecodeUserPayload(data io.Reader) (*model.User, error) {
 	return &user, nil
 }
 
-// DecodeURLPayload unserialize JSON subscription object.
-func DecodeURLPayload(data io.Reader) (string, error) {
+func decodeURLPayload(data io.Reader) (string, error) {
 	type payload struct {
 		URL string `json:"url"`
 	}
@@ -52,8 +48,7 @@ func DecodeURLPayload(data io.Reader) (string, error) {
 	return p.URL, nil
 }
 
-// DecodeEntryStatusPayload unserialize JSON entry statuses object.
-func DecodeEntryStatusPayload(data io.Reader) ([]int64, string, error) {
+func decodeEntryStatusPayload(data io.Reader) ([]int64, string, error) {
 	type payload struct {
 		EntryIDs []int64 `json:"entry_ids"`
 		Status   string  `json:"status"`
@@ -68,8 +63,7 @@ func DecodeEntryStatusPayload(data io.Reader) ([]int64, string, error) {
 	return p.EntryIDs, p.Status, nil
 }
 
-// DecodeFeedCreationPayload unserialize JSON feed creation object.
-func DecodeFeedCreationPayload(data io.Reader) (string, int64, error) {
+func decodeFeedCreationPayload(data io.Reader) (string, int64, error) {
 	type payload struct {
 		FeedURL    string `json:"feed_url"`
 		CategoryID int64  `json:"category_id"`
@@ -84,8 +78,7 @@ func DecodeFeedCreationPayload(data io.Reader) (string, int64, error) {
 	return p.FeedURL, p.CategoryID, nil
 }
 
-// DecodeFeedModificationPayload unserialize JSON feed object.
-func DecodeFeedModificationPayload(data io.Reader) (*model.Feed, error) {
+func decodeFeedModificationPayload(data io.Reader) (*model.Feed, error) {
 	var feed model.Feed
 
 	decoder := json.NewDecoder(data)
@@ -96,8 +89,7 @@ func DecodeFeedModificationPayload(data io.Reader) (*model.Feed, error) {
 	return &feed, nil
 }
 
-// DecodeCategoryPayload unserialize JSON category object.
-func DecodeCategoryPayload(data io.Reader) (*model.Category, error) {
+func decodeCategoryPayload(data io.Reader) (*model.Category, error) {
 	var category model.Category
 
 	decoder := json.NewDecoder(data)
diff --git a/server/api/controller/subscription.go b/api/subscription.go
similarity index 72%
rename from server/api/controller/subscription.go
rename to api/subscription.go
index aa2a26f9c9ff0e17444b1251cb6d235986388bac..8a8fefff3b9439c571336c76409c9f61d68bb8ff 100644
--- a/server/api/controller/subscription.go
+++ b/api/subscription.go
@@ -8,14 +8,13 @@ import (
 	"errors"
 	"fmt"
 
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/reader/subscription"
-	"github.com/miniflux/miniflux/server/api/payload"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // GetSubscriptions is the API handler to find subscriptions.
-func (c *Controller) GetSubscriptions(ctx *core.Context, request *core.Request, response *core.Response) {
-	websiteURL, err := payload.DecodeURLPayload(request.Body())
+func (c *Controller) GetSubscriptions(ctx *handler.Context, request *handler.Request, response *handler.Response) {
+	websiteURL, err := decodeURLPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
diff --git a/server/api/controller/user.go b/api/user.go
similarity index 81%
rename from server/api/controller/user.go
rename to api/user.go
index a9259081bab5972daabf461cf08b0a433f4c235d..96571b03ced1f3ab3fe4a68dba584a011c8978b2 100644
--- a/server/api/controller/user.go
+++ b/api/user.go
@@ -7,18 +7,17 @@ package api
 import (
 	"errors"
 
-	"github.com/miniflux/miniflux/server/api/payload"
-	"github.com/miniflux/miniflux/server/core"
+	"github.com/miniflux/miniflux/http/handler"
 )
 
 // CreateUser is the API handler to create a new user.
-func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) CreateUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	if !ctx.IsAdminUser() {
 		response.JSON().Forbidden()
 		return
 	}
 
-	user, err := payload.DecodeUserPayload(request.Body())
+	user, err := decodeUserPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
@@ -45,7 +44,7 @@ func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, respon
 }
 
 // UpdateUser is the API handler to update the given user.
-func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	if !ctx.IsAdminUser() {
 		response.JSON().Forbidden()
 		return
@@ -57,7 +56,7 @@ func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, respon
 		return
 	}
 
-	user, err := payload.DecodeUserPayload(request.Body())
+	user, err := decodeUserPayload(request.Body())
 	if err != nil {
 		response.JSON().BadRequest(err)
 		return
@@ -89,7 +88,7 @@ func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, respon
 }
 
 // Users is the API handler to get the list of users.
-func (c *Controller) Users(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Users(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	if !ctx.IsAdminUser() {
 		response.JSON().Forbidden()
 		return
@@ -105,7 +104,7 @@ func (c *Controller) Users(ctx *core.Context, request *core.Request, response *c
 }
 
 // UserByID is the API handler to fetch the given user by the ID.
-func (c *Controller) UserByID(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UserByID(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	if !ctx.IsAdminUser() {
 		response.JSON().Forbidden()
 		return
@@ -132,7 +131,7 @@ func (c *Controller) UserByID(ctx *core.Context, request *core.Request, response
 }
 
 // UserByUsername is the API handler to fetch the given user by the username.
-func (c *Controller) UserByUsername(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UserByUsername(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	if !ctx.IsAdminUser() {
 		response.JSON().Forbidden()
 		return
@@ -154,7 +153,7 @@ func (c *Controller) UserByUsername(ctx *core.Context, request *core.Request, re
 }
 
 // RemoveUser is the API handler to remove an existing user.
-func (c *Controller) RemoveUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RemoveUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	if !ctx.IsAdminUser() {
 		response.JSON().Forbidden()
 		return
diff --git a/cli/cli.go b/cli/cli.go
new file mode 100644
index 0000000000000000000000000000000000000000..67131e60a264a1fafdf5a297c5f348af19b56e3f
--- /dev/null
+++ b/cli/cli.go
@@ -0,0 +1,58 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package cli
+
+import (
+	"flag"
+	"fmt"
+
+	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/daemon"
+	"github.com/miniflux/miniflux/storage"
+	"github.com/miniflux/miniflux/version"
+)
+
+// Parse parses command line arguments.
+func Parse() {
+	flagInfo := flag.Bool("info", false, "Show application information")
+	flagVersion := flag.Bool("version", false, "Show application version")
+	flagMigrate := flag.Bool("migrate", false, "Migrate database schema")
+	flagFlushSessions := flag.Bool("flush-sessions", false, "Flush all sessions (disconnect users)")
+	flagCreateAdmin := flag.Bool("create-admin", false, "Create admin user")
+	flag.Parse()
+
+	cfg := config.NewConfig()
+	store := storage.NewStorage(
+		cfg.Get("DATABASE_URL", config.DefaultDatabaseURL),
+		cfg.GetInt("DATABASE_MAX_CONNS", config.DefaultDatabaseMaxConns),
+	)
+
+	if *flagInfo {
+		info()
+		return
+	}
+
+	if *flagVersion {
+		fmt.Println(version.Version)
+		return
+	}
+
+	if *flagMigrate {
+		store.Migrate()
+		return
+	}
+
+	if *flagFlushSessions {
+		flushSessions(store)
+		return
+	}
+
+	if *flagCreateAdmin {
+		createAdmin(store)
+		return
+	}
+
+	daemon.Run(cfg, store)
+}
diff --git a/cli/create_admin.go b/cli/create_admin.go
new file mode 100644
index 0000000000000000000000000000000000000000..9af8df3383f3bdafe1422760cb14b0abb2bb54dd
--- /dev/null
+++ b/cli/create_admin.go
@@ -0,0 +1,52 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package cli
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/miniflux/miniflux/model"
+	"github.com/miniflux/miniflux/storage"
+
+	"golang.org/x/crypto/ssh/terminal"
+)
+
+func askCredentials() (string, string) {
+	reader := bufio.NewReader(os.Stdin)
+
+	fmt.Print("Enter Username: ")
+	username, _ := reader.ReadString('\n')
+
+	fmt.Print("Enter Password: ")
+	bytePassword, _ := terminal.ReadPassword(0)
+
+	fmt.Printf("\n")
+	return strings.TrimSpace(username), strings.TrimSpace(string(bytePassword))
+}
+
+func createAdmin(store *storage.Storage) {
+	user := &model.User{
+		Username: os.Getenv("ADMIN_USERNAME"),
+		Password: os.Getenv("ADMIN_PASSWORD"),
+		IsAdmin:  true,
+	}
+
+	if user.Username == "" || user.Password == "" {
+		user.Username, user.Password = askCredentials()
+	}
+
+	if err := user.ValidateUserCreation(); err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	if err := store.CreateUser(user); err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
diff --git a/cli/flush_sessions.go b/cli/flush_sessions.go
new file mode 100644
index 0000000000000000000000000000000000000000..06a56d0b2fb455bf5fd3e14b8e0b2fb0080172fd
--- /dev/null
+++ b/cli/flush_sessions.go
@@ -0,0 +1,20 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package cli
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/miniflux/miniflux/storage"
+)
+
+func flushSessions(store *storage.Storage) {
+	fmt.Println("Flushing all sessions (disconnect users)")
+	if err := store.FlushAllSessions(); err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
diff --git a/cli/info.go b/cli/info.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ae1cefea002008139bf822e2fce58918eed1e51
--- /dev/null
+++ b/cli/info.go
@@ -0,0 +1,18 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package cli
+
+import (
+	"fmt"
+	"runtime"
+
+	"github.com/miniflux/miniflux/version"
+)
+
+func info() {
+	fmt.Println("Version:", version.Version)
+	fmt.Println("Build Date:", version.BuildDate)
+	fmt.Println("Go Version:", runtime.Version())
+}
diff --git a/daemon/daemon.go b/daemon/daemon.go
new file mode 100644
index 0000000000000000000000000000000000000000..494e03563fcaf9d12e193949b9cf1e9fdab3d3db
--- /dev/null
+++ b/daemon/daemon.go
@@ -0,0 +1,46 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package daemon
+
+import (
+	"context"
+	"os"
+	"os/signal"
+	"time"
+
+	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/logger"
+	"github.com/miniflux/miniflux/reader/feed"
+	"github.com/miniflux/miniflux/scheduler"
+	"github.com/miniflux/miniflux/storage"
+)
+
+// Run starts the daemon.
+func Run(cfg *config.Config, store *storage.Storage) {
+	logger.Info("Starting Miniflux...")
+
+	stop := make(chan os.Signal, 1)
+	signal.Notify(stop, os.Interrupt)
+
+	feedHandler := feed.NewFeedHandler(store)
+	pool := scheduler.NewWorkerPool(feedHandler, cfg.GetInt("WORKER_POOL_SIZE", config.DefaultWorkerPoolSize))
+	server := newServer(cfg, store, pool, feedHandler)
+
+	scheduler.NewFeedScheduler(
+		store,
+		pool,
+		cfg.GetInt("POLLING_FREQUENCY", config.DefaultPollingFrequency),
+		cfg.GetInt("BATCH_SIZE", config.DefaultBatchSize),
+	)
+
+	scheduler.NewSessionScheduler(store, config.DefaultSessionCleanupFrequency)
+
+	<-stop
+	logger.Info("Shutting down the server...")
+	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
+	server.Shutdown(ctx)
+	store.Close()
+	logger.Info("Server gracefully stopped")
+}
diff --git a/server/routes.go b/daemon/routes.go
similarity index 91%
rename from server/routes.go
rename to daemon/routes.go
index e56b8cc81a2c93029599757b5cc6495fdcf11327..0c266f4a141b1ddd9d9b4e2672929a9a37d36232 100644
--- a/server/routes.go
+++ b/daemon/routes.go
@@ -1,47 +1,46 @@
-// Copyright 2017 Frédéric Guillot. All rights reserved.
+// Copyright 2018 Frédéric Guillot. All rights reserved.
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package server
+package daemon
 
 import (
 	"net/http"
 
-	"github.com/miniflux/miniflux/scheduler"
-
+	"github.com/miniflux/miniflux/api"
 	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/fever"
+	"github.com/miniflux/miniflux/http/handler"
+	"github.com/miniflux/miniflux/http/middleware"
 	"github.com/miniflux/miniflux/locale"
 	"github.com/miniflux/miniflux/reader/feed"
 	"github.com/miniflux/miniflux/reader/opml"
-	api_controller "github.com/miniflux/miniflux/server/api/controller"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/fever"
-	"github.com/miniflux/miniflux/server/middleware"
-	"github.com/miniflux/miniflux/server/template"
-	ui_controller "github.com/miniflux/miniflux/server/ui/controller"
+	"github.com/miniflux/miniflux/scheduler"
 	"github.com/miniflux/miniflux/storage"
+	"github.com/miniflux/miniflux/template"
+	"github.com/miniflux/miniflux/ui"
 
 	"github.com/gorilla/mux"
 )
 
-func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool) *mux.Router {
+func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool) *mux.Router {
 	router := mux.NewRouter()
 	translator := locale.Load()
 	templateEngine := template.NewEngine(cfg, router, translator)
 
-	apiController := api_controller.NewController(store, feedHandler)
+	apiController := api.NewController(store, feedHandler)
 	feverController := fever.NewController(store)
-	uiController := ui_controller.NewController(cfg, store, pool, feedHandler, opml.NewHandler(store))
+	uiController := ui.NewController(cfg, store, pool, feedHandler, opml.NewHandler(store))
 
-	apiHandler := core.NewHandler(cfg, store, router, templateEngine, translator, middleware.NewChain(
+	apiHandler := handler.NewHandler(cfg, store, router, templateEngine, translator, middleware.NewChain(
 		middleware.NewBasicAuthMiddleware(store).Handler,
 	))
 
-	feverHandler := core.NewHandler(cfg, store, router, templateEngine, translator, middleware.NewChain(
+	feverHandler := handler.NewHandler(cfg, store, router, templateEngine, translator, middleware.NewChain(
 		middleware.NewFeverMiddleware(store).Handler,
 	))
 
-	uiHandler := core.NewHandler(cfg, store, router, templateEngine, translator, middleware.NewChain(
+	uiHandler := handler.NewHandler(cfg, store, router, templateEngine, translator, middleware.NewChain(
 		middleware.NewUserSessionMiddleware(store, router).Handler,
 		middleware.NewSessionMiddleware(cfg, store).Handler,
 	))
diff --git a/server/server.go b/daemon/server.go
similarity index 83%
rename from server/server.go
rename to daemon/server.go
index 4627b83c05f91db740ad431810a0c95336344fec..dfdfb79e6b12f00805aa4c1bfd8e88a7255375c2 100644
--- a/server/server.go
+++ b/daemon/server.go
@@ -1,30 +1,24 @@
-// Copyright 2017 Frédéric Guillot. All rights reserved.
+// Copyright 2018 Frédéric Guillot. All rights reserved.
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package server
+package daemon
 
 import (
 	"crypto/tls"
 	"net/http"
 	"time"
 
-	"github.com/gorilla/mux"
-	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/scheduler"
-	"golang.org/x/crypto/acme/autocert"
-
 	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/reader/feed"
+	"github.com/miniflux/miniflux/scheduler"
 	"github.com/miniflux/miniflux/storage"
-)
 
-// NewServer returns a new HTTP server.
-func NewServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler) *http.Server {
-	return startServer(cfg, getRoutes(cfg, store, feedHandler, pool))
-}
+	"golang.org/x/crypto/acme/autocert"
+)
 
-func startServer(cfg *config.Config, handler *mux.Router) *http.Server {
+func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler) *http.Server {
 	certFile := cfg.Get("CERT_FILE", config.DefaultCertFile)
 	keyFile := cfg.Get("KEY_FILE", config.DefaultKeyFile)
 	certDomain := cfg.Get("CERT_DOMAIN", config.DefaultCertDomain)
@@ -34,7 +28,7 @@ func startServer(cfg *config.Config, handler *mux.Router) *http.Server {
 		WriteTimeout: 10 * time.Second,
 		IdleTimeout:  60 * time.Second,
 		Addr:         cfg.Get("LISTEN_ADDR", config.DefaultListenAddr),
-		Handler:      handler,
+		Handler:      routes(cfg, store, feedHandler, pool),
 	}
 
 	if certDomain != "" && certCache != "" {
diff --git a/server/fever/fever.go b/fever/fever.go
similarity index 93%
rename from server/fever/fever.go
rename to fever/fever.go
index 6690b7f8a0dd486c17be66342f10ea4db1c6dc53..54a4416030706eeda31f0b63f00b177f2b036679 100644
--- a/server/fever/fever.go
+++ b/fever/fever.go
@@ -9,10 +9,10 @@ import (
 	"strings"
 	"time"
 
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/integration"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
 	"github.com/miniflux/miniflux/storage"
 )
 
@@ -129,7 +129,7 @@ type Controller struct {
 }
 
 // Handler handles Fever API calls
-func (c *Controller) Handler(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Handler(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	switch {
 	case request.HasQueryParam("groups"):
 		c.handleGroups(ctx, request, response)
@@ -174,7 +174,7 @@ The “Sparks” super group is not included in this response and is composed of
 is_spark equal to 1.
 
 */
-func (c *Controller) handleGroups(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleGroups(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Fetching groups for userID=%d", userID)
 
@@ -224,7 +224,7 @@ should be limited to feeds with an is_spark equal to 0.
 
 For the “Sparks” super group the items should be limited to feeds with an is_spark equal to 1.
 */
-func (c *Controller) handleFeeds(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleFeeds(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Fetching feeds for userID=%d", userID)
 
@@ -277,7 +277,7 @@ A PHP/HTML example:
 
 	echo '<img src="data:'.$favicon['data'].'">';
 */
-func (c *Controller) handleFavicons(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleFavicons(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Fetching favicons for userID=%d", userID)
 
@@ -330,7 +330,7 @@ Three optional arguments control determine the items included in the response.
 	(added in API version 2)
 
 */
-func (c *Controller) handleItems(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleItems(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	var result itemsResponse
 
 	userID := ctx.UserID()
@@ -414,7 +414,7 @@ with the remote Fever installation.
 A request with the unread_item_ids argument will return one additional member:
     unread_item_ids (string/comma-separated list of positive integers)
 */
-func (c *Controller) handleUnreadItems(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleUnreadItems(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Fetching unread items for userID=%d", userID)
 
@@ -445,7 +445,7 @@ with the remote Fever installation.
 
 	saved_item_ids (string/comma-separated list of positive integers)
 */
-func (c *Controller) handleSavedItems(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleSavedItems(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Fetching saved items for userID=%d", userID)
 
@@ -473,7 +473,7 @@ func (c *Controller) handleSavedItems(ctx *core.Context, request *core.Request,
 	as=? where ? is replaced with read, saved or unsaved
 	id=? where ? is replaced with the id of the item to modify
 */
-func (c *Controller) handleWriteItems(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleWriteItems(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Receiving mark=item call for userID=%d", userID)
 
@@ -527,7 +527,7 @@ func (c *Controller) handleWriteItems(ctx *core.Context, request *core.Request,
 	id=? where ? is replaced with the id of the feed or group to modify
 	before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
 */
-func (c *Controller) handleWriteFeeds(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleWriteFeeds(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Receiving mark=feed call for userID=%d", userID)
 
@@ -567,7 +567,7 @@ func (c *Controller) handleWriteFeeds(ctx *core.Context, request *core.Request,
 	id=? where ? is replaced with the id of the feed or group to modify
 	before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
 */
-func (c *Controller) handleWriteGroups(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) handleWriteGroups(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	userID := ctx.UserID()
 	logger.Debug("[Fever] Receiving mark=group call for userID=%d", userID)
 
diff --git a/server/ui/filter/image_proxy_filter.go b/filter/image_proxy_filter.go
similarity index 95%
rename from server/ui/filter/image_proxy_filter.go
rename to filter/image_proxy_filter.go
index 12c9da635aa1c8d94caf9c0212f491bae439af59..ef6d39733b09f304a3dc35264942ba6885fd5612 100644
--- a/server/ui/filter/image_proxy_filter.go
+++ b/filter/image_proxy_filter.go
@@ -8,7 +8,7 @@ import (
 	"encoding/base64"
 	"strings"
 
-	"github.com/miniflux/miniflux/server/route"
+	"github.com/miniflux/miniflux/http/route"
 	"github.com/miniflux/miniflux/url"
 
 	"github.com/PuerkitoBio/goquery"
diff --git a/server/ui/filter/image_proxy_filter_test.go b/filter/image_proxy_filter_test.go
similarity index 100%
rename from server/ui/filter/image_proxy_filter_test.go
rename to filter/image_proxy_filter_test.go
diff --git a/generate.go b/generate.go
index cc3bc5e2253a7122464dd7941679dd1421599c66..266a9fd600986a8a06d4c89abab4213dc1f6828d 100644
--- a/generate.go
+++ b/generate.go
@@ -111,10 +111,10 @@ func generateFile(serializer, pkg, mapName, pattern, output string) {
 
 func main() {
 	generateFile("none", "sql", "SqlMap", "sql/*.sql", "sql/sql.go")
-	generateFile("base64", "static", "Binaries", "server/static/bin/*", "server/static/bin.go")
-	generateFile("css", "static", "Stylesheets", "server/static/css/*.css", "server/static/css.go")
-	generateFile("js", "static", "Javascript", "server/static/js/*.js", "server/static/js.go")
-	generateFile("none", "template", "templateViewsMap", "server/template/html/*.html", "server/template/views.go")
-	generateFile("none", "template", "templateCommonMap", "server/template/html/common/*.html", "server/template/common.go")
+	generateFile("base64", "static", "Binaries", "ui/static/bin/*", "ui/static/bin.go")
+	generateFile("css", "static", "Stylesheets", "ui/static/css/*.css", "ui/static/css.go")
+	generateFile("js", "static", "Javascript", "ui/static/js/*.js", "ui/static/js.go")
+	generateFile("none", "template", "templateViewsMap", "template/html/*.html", "template/views.go")
+	generateFile("none", "template", "templateCommonMap", "template/html/common/*.html", "template/common.go")
 	generateFile("none", "locale", "translations", "locale/translations/*.json", "locale/translations.go")
 }
diff --git a/server/cookie/cookie.go b/http/cookie/cookie.go
similarity index 100%
rename from server/cookie/cookie.go
rename to http/cookie/cookie.go
diff --git a/server/core/context.go b/http/handler/context.go
similarity index 92%
rename from server/core/context.go
rename to http/handler/context.go
index 8145b4718cbb72594a9a540a3488738ad54a99d6..a04a8166d49c85f30da4ab04e14cb1caec8457f4 100644
--- a/server/core/context.go
+++ b/http/handler/context.go
@@ -2,17 +2,17 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package core
+package handler
 
 import (
 	"net/http"
 
 	"github.com/miniflux/miniflux/crypto"
+	"github.com/miniflux/miniflux/http/middleware"
+	"github.com/miniflux/miniflux/http/route"
 	"github.com/miniflux/miniflux/locale"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/middleware"
-	"github.com/miniflux/miniflux/server/route"
 	"github.com/miniflux/miniflux/storage"
 
 	"github.com/gorilla/mux"
@@ -155,6 +155,6 @@ func (c *Context) Route(name string, args ...interface{}) string {
 }
 
 // NewContext creates a new Context.
-func NewContext(w http.ResponseWriter, r *http.Request, store *storage.Storage, router *mux.Router, translator *locale.Translator) *Context {
-	return &Context{writer: w, request: r, store: store, router: router, translator: translator}
+func NewContext(r *http.Request, store *storage.Storage, router *mux.Router, translator *locale.Translator) *Context {
+	return &Context{request: r, store: store, router: router, translator: translator}
 }
diff --git a/server/core/handler.go b/http/handler/handler.go
similarity index 81%
rename from server/core/handler.go
rename to http/handler/handler.go
index e6aca98ac0e29e97a3ad6df92ce479bda98247a9..3dd1d1b11982734cf5d70d5f061edba0086586e9 100644
--- a/server/core/handler.go
+++ b/http/handler/handler.go
@@ -2,26 +2,26 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package core
+package handler
 
 import (
 	"net/http"
 	"time"
 
 	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/http/middleware"
 	"github.com/miniflux/miniflux/locale"
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/middleware"
-	"github.com/miniflux/miniflux/server/template"
 	"github.com/miniflux/miniflux/storage"
+	"github.com/miniflux/miniflux/template"
 	"github.com/miniflux/miniflux/timer"
 
 	"github.com/gorilla/mux"
 	"github.com/tomasen/realip"
 )
 
-// HandlerFunc is an application HTTP handler.
-type HandlerFunc func(ctx *Context, request *Request, response *Response)
+// ControllerFunc is an application HTTP handler.
+type ControllerFunc func(ctx *Context, request *Request, response *Response)
 
 // Handler manages HTTP handlers and middlewares.
 type Handler struct {
@@ -34,7 +34,7 @@ type Handler struct {
 }
 
 // Use is a wrapper around an HTTP handler.
-func (h *Handler) Use(f HandlerFunc) http.Handler {
+func (h *Handler) Use(f ControllerFunc) http.Handler {
 	return h.middleware.WrapFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		defer timer.ExecutionTime(time.Now(), r.URL.Path)
 		logger.Debug("[HTTP] %s %s %s", realip.RealIP(r), r.Method, r.URL.Path)
@@ -43,8 +43,8 @@ func (h *Handler) Use(f HandlerFunc) http.Handler {
 			h.cfg.IsHTTPS = true
 		}
 
-		ctx := NewContext(w, r, h.store, h.router, h.translator)
-		request := NewRequest(w, r)
+		ctx := NewContext(r, h.store, h.router, h.translator)
+		request := NewRequest(r)
 		response := NewResponse(w, r, h.template)
 
 		if ctx.IsAuthenticated() {
diff --git a/server/core/html_response.go b/http/handler/html_response.go
similarity index 96%
rename from server/core/html_response.go
rename to http/handler/html_response.go
index a1941631a7e735fb176df04ff3f4f9f33744cef5..26e52708f8687b220e56d3e8ff8246d1a93ed82e 100644
--- a/server/core/html_response.go
+++ b/http/handler/html_response.go
@@ -2,13 +2,13 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package core
+package handler
 
 import (
 	"net/http"
 
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/template"
+	"github.com/miniflux/miniflux/template"
 )
 
 // HTMLResponse handles HTML responses.
diff --git a/server/core/json_response.go b/http/handler/json_response.go
similarity index 99%
rename from server/core/json_response.go
rename to http/handler/json_response.go
index 8ee0b7f10d462cb6fbfd4e28c209c27cb9ff587c..a79268c9c667e704f5df03246315d25335156637 100644
--- a/server/core/json_response.go
+++ b/http/handler/json_response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package core
+package handler
 
 import (
 	"encoding/json"
diff --git a/server/core/request.go b/http/handler/request.go
similarity index 93%
rename from server/core/request.go
rename to http/handler/request.go
index f3a365231f1318c7b0d6f3354e9a0c2ae06dd673..7289a70f3f143c7047e5a1e30ae9feabc6748141 100644
--- a/server/core/request.go
+++ b/http/handler/request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package core
+package handler
 
 import (
 	"fmt"
@@ -17,7 +17,6 @@ import (
 
 // Request is a thin wrapper around "http.Request".
 type Request struct {
-	writer  http.ResponseWriter
 	request *http.Request
 }
 
@@ -119,7 +118,7 @@ func (r *Request) HasQueryParam(param string) bool {
 	return ok
 }
 
-// NewRequest returns a new Request struct.
-func NewRequest(w http.ResponseWriter, r *http.Request) *Request {
-	return &Request{writer: w, request: r}
+// NewRequest returns a new Request.
+func NewRequest(r *http.Request) *Request {
+	return &Request{r}
 }
diff --git a/server/core/response.go b/http/handler/response.go
similarity index 97%
rename from server/core/response.go
rename to http/handler/response.go
index f3fc7a104159b5d1c2f095e00281b56e2fe35757..34980a3633558cd6e5bf3197574a2f53021ae25a 100644
--- a/server/core/response.go
+++ b/http/handler/response.go
@@ -2,13 +2,13 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package core
+package handler
 
 import (
 	"net/http"
 	"time"
 
-	"github.com/miniflux/miniflux/server/template"
+	"github.com/miniflux/miniflux/template"
 )
 
 // Response handles HTTP responses.
diff --git a/server/core/xml_response.go b/http/handler/xml_response.go
similarity index 97%
rename from server/core/xml_response.go
rename to http/handler/xml_response.go
index e9a2d3fec8283b2ea1894317b17834c683179baa..b5a24cab5cc5bc4c3b3597c010a0bfe162ba5743 100644
--- a/server/core/xml_response.go
+++ b/http/handler/xml_response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package core
+package handler
 
 import (
 	"fmt"
diff --git a/server/middleware/basic_auth.go b/http/middleware/basic_auth.go
similarity index 100%
rename from server/middleware/basic_auth.go
rename to http/middleware/basic_auth.go
diff --git a/server/middleware/context_keys.go b/http/middleware/context_keys.go
similarity index 100%
rename from server/middleware/context_keys.go
rename to http/middleware/context_keys.go
diff --git a/server/middleware/fever.go b/http/middleware/fever.go
similarity index 100%
rename from server/middleware/fever.go
rename to http/middleware/fever.go
diff --git a/server/middleware/middleware.go b/http/middleware/middleware.go
similarity index 100%
rename from server/middleware/middleware.go
rename to http/middleware/middleware.go
diff --git a/server/middleware/session.go b/http/middleware/session.go
similarity index 98%
rename from server/middleware/session.go
rename to http/middleware/session.go
index ad02bb244ab2eb325b805fcdf61ce4a5783f4848..a0e9fbd8db3ea2b2e5f63cfe882b70051c5df9e1 100644
--- a/server/middleware/session.go
+++ b/http/middleware/session.go
@@ -9,9 +9,9 @@ import (
 	"net/http"
 
 	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/http/cookie"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/cookie"
 	"github.com/miniflux/miniflux/storage"
 )
 
diff --git a/server/middleware/user_session.go b/http/middleware/user_session.go
similarity index 96%
rename from server/middleware/user_session.go
rename to http/middleware/user_session.go
index 3d1dae614dd67d216a59e2d49f26c3d8f7d74b31..d67445f0ac11be58253785184c70e28230d1d7ee 100644
--- a/server/middleware/user_session.go
+++ b/http/middleware/user_session.go
@@ -8,10 +8,10 @@ import (
 	"context"
 	"net/http"
 
+	"github.com/miniflux/miniflux/http/cookie"
+	"github.com/miniflux/miniflux/http/route"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/cookie"
-	"github.com/miniflux/miniflux/server/route"
 	"github.com/miniflux/miniflux/storage"
 
 	"github.com/gorilla/mux"
diff --git a/server/route/route.go b/http/route/route.go
similarity index 100%
rename from server/route/route.go
rename to http/route/route.go
diff --git a/locale/translations.go b/locale/translations.go
index 132ed314e5ecd07ebc6f99f0c197197826cf6155..ac073c8ec985e7f76eaec2a4ceda56af0c79063a 100644
--- a/locale/translations.go
+++ b/locale/translations.go
@@ -1,5 +1,5 @@
 // Code generated by go generate; DO NOT EDIT.
-// 2017-12-31 18:38:42.071995118 -0800 PST m=+0.052826276
+// 2018-01-02 21:59:10.103098936 -0800 PST m=+0.030474265
 
 package locale
 
diff --git a/main.go b/main.go
index a252c61005efc38ec2640644159007aa749e03fb..79dcf2708178be932a8dc3e335c1e84f389fce6e 100644
--- a/main.go
+++ b/main.go
@@ -6,140 +6,18 @@ package main
 
 //go:generate go run generate.go
 //go:generate gofmt -s -w sql/sql.go
-//go:generate gofmt -s -w server/static/css.go
-//go:generate gofmt -s -w server/static/bin.go
-//go:generate gofmt -s -w server/static/js.go
-//go:generate gofmt -s -w server/template/views.go
-//go:generate gofmt -s -w server/template/common.go
+//go:generate gofmt -s -w ui/static/css.go
+//go:generate gofmt -s -w ui/static/bin.go
+//go:generate gofmt -s -w ui/static/js.go
+//go:generate gofmt -s -w template/views.go
+//go:generate gofmt -s -w template/common.go
 //go:generate gofmt -s -w locale/translations.go
 
 import (
-	"bufio"
-	"context"
-	"flag"
-	"fmt"
-	"os"
-	"os/signal"
-	"runtime"
-	"strings"
-	"time"
-
-	"github.com/miniflux/miniflux/config"
-	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/reader/feed"
-	"github.com/miniflux/miniflux/scheduler"
-	"github.com/miniflux/miniflux/server"
-	"github.com/miniflux/miniflux/storage"
-	"github.com/miniflux/miniflux/version"
-
 	_ "github.com/lib/pq"
-	"golang.org/x/crypto/ssh/terminal"
+	"github.com/miniflux/miniflux/cli"
 )
 
-func run(cfg *config.Config, store *storage.Storage) {
-	logger.Info("Starting Miniflux...")
-
-	stop := make(chan os.Signal, 1)
-	signal.Notify(stop, os.Interrupt)
-
-	feedHandler := feed.NewFeedHandler(store)
-	pool := scheduler.NewWorkerPool(feedHandler, cfg.GetInt("WORKER_POOL_SIZE", config.DefaultWorkerPoolSize))
-	server := server.NewServer(cfg, store, pool, feedHandler)
-
-	scheduler.NewFeedScheduler(
-		store,
-		pool,
-		cfg.GetInt("POLLING_FREQUENCY", config.DefaultPollingFrequency),
-		cfg.GetInt("BATCH_SIZE", config.DefaultBatchSize),
-	)
-
-	scheduler.NewSessionScheduler(store, config.DefaultSessionCleanupFrequency)
-
-	<-stop
-	logger.Info("Shutting down the server...")
-	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
-	server.Shutdown(ctx)
-	store.Close()
-	logger.Info("Server gracefully stopped")
-}
-
-func askCredentials() (string, string) {
-	reader := bufio.NewReader(os.Stdin)
-
-	fmt.Print("Enter Username: ")
-	username, _ := reader.ReadString('\n')
-
-	fmt.Print("Enter Password: ")
-	bytePassword, _ := terminal.ReadPassword(0)
-
-	fmt.Printf("\n")
-	return strings.TrimSpace(username), strings.TrimSpace(string(bytePassword))
-}
-
 func main() {
-	flagInfo := flag.Bool("info", false, "Show application information")
-	flagVersion := flag.Bool("version", false, "Show application version")
-	flagMigrate := flag.Bool("migrate", false, "Migrate database schema")
-	flagFlushSessions := flag.Bool("flush-sessions", false, "Flush all sessions (disconnect users)")
-	flagCreateAdmin := flag.Bool("create-admin", false, "Create admin user")
-	flag.Parse()
-
-	cfg := config.NewConfig()
-	store := storage.NewStorage(
-		cfg.Get("DATABASE_URL", config.DefaultDatabaseURL),
-		cfg.GetInt("DATABASE_MAX_CONNS", config.DefaultDatabaseMaxConns),
-	)
-
-	if *flagInfo {
-		fmt.Println("Version:", version.Version)
-		fmt.Println("Build Date:", version.BuildDate)
-		fmt.Println("Go Version:", runtime.Version())
-		return
-	}
-
-	if *flagVersion {
-		fmt.Println(version.Version)
-		return
-	}
-
-	if *flagMigrate {
-		store.Migrate()
-		return
-	}
-
-	if *flagFlushSessions {
-		fmt.Println("Flushing all sessions (disconnect users)")
-		if err := store.FlushAllSessions(); err != nil {
-			fmt.Println(err)
-			os.Exit(1)
-		}
-		return
-	}
-
-	if *flagCreateAdmin {
-		user := &model.User{
-			Username: os.Getenv("ADMIN_USERNAME"),
-			Password: os.Getenv("ADMIN_PASSWORD"),
-			IsAdmin:  true,
-		}
-
-		if user.Username == "" || user.Password == "" {
-			user.Username, user.Password = askCredentials()
-		}
-
-		if err := user.ValidateUserCreation(); err != nil {
-			fmt.Println(err)
-			os.Exit(1)
-		}
-
-		if err := store.CreateUser(user); err != nil {
-			fmt.Println(err)
-			os.Exit(1)
-		}
-
-		return
-	}
-
-	run(cfg, store)
+	cli.Parse()
 }
diff --git a/server/oauth2/google.go b/oauth2/google.go
similarity index 100%
rename from server/oauth2/google.go
rename to oauth2/google.go
diff --git a/server/oauth2/manager.go b/oauth2/manager.go
similarity index 100%
rename from server/oauth2/manager.go
rename to oauth2/manager.go
diff --git a/server/oauth2/profile.go b/oauth2/profile.go
similarity index 100%
rename from server/oauth2/profile.go
rename to oauth2/profile.go
diff --git a/server/oauth2/provider.go b/oauth2/provider.go
similarity index 100%
rename from server/oauth2/provider.go
rename to oauth2/provider.go
diff --git a/sql/sql.go b/sql/sql.go
index f098915cc5673f80c714bad72cb9da6d38e7182b..36f344e98a5af497cd916df3b1bc327e4edf7369 100644
--- a/sql/sql.go
+++ b/sql/sql.go
@@ -1,5 +1,5 @@
 // Code generated by go generate; DO NOT EDIT.
-// 2017-12-24 14:32:38.84708161 -0800 PST m=+0.004106505
+// 2018-01-02 21:59:10.075345511 -0800 PST m=+0.002720840
 
 package sql
 
diff --git a/server/template/common.go b/template/common.go
similarity index 99%
rename from server/template/common.go
rename to template/common.go
index f0a481d547fa6d52ba294fd147509ce6711ac688..7ec578fd15b43ec9295407d967983c10951ad975 100644
--- a/server/template/common.go
+++ b/template/common.go
@@ -1,5 +1,5 @@
 // Code generated by go generate; DO NOT EDIT.
-// 2017-12-31 18:38:42.07097409 -0800 PST m=+0.051805248
+// 2018-01-02 21:59:10.101985953 -0800 PST m=+0.029361282
 
 package template
 
diff --git a/server/template/html/about.html b/template/html/about.html
similarity index 100%
rename from server/template/html/about.html
rename to template/html/about.html
diff --git a/server/template/html/add_subscription.html b/template/html/add_subscription.html
similarity index 100%
rename from server/template/html/add_subscription.html
rename to template/html/add_subscription.html
diff --git a/server/template/html/categories.html b/template/html/categories.html
similarity index 100%
rename from server/template/html/categories.html
rename to template/html/categories.html
diff --git a/server/template/html/category_entries.html b/template/html/category_entries.html
similarity index 100%
rename from server/template/html/category_entries.html
rename to template/html/category_entries.html
diff --git a/server/template/html/choose_subscription.html b/template/html/choose_subscription.html
similarity index 100%
rename from server/template/html/choose_subscription.html
rename to template/html/choose_subscription.html
diff --git a/server/template/html/common/entry_pagination.html b/template/html/common/entry_pagination.html
similarity index 100%
rename from server/template/html/common/entry_pagination.html
rename to template/html/common/entry_pagination.html
diff --git a/server/template/html/common/layout.html b/template/html/common/layout.html
similarity index 100%
rename from server/template/html/common/layout.html
rename to template/html/common/layout.html
diff --git a/server/template/html/common/pagination.html b/template/html/common/pagination.html
similarity index 100%
rename from server/template/html/common/pagination.html
rename to template/html/common/pagination.html
diff --git a/server/template/html/create_category.html b/template/html/create_category.html
similarity index 100%
rename from server/template/html/create_category.html
rename to template/html/create_category.html
diff --git a/server/template/html/create_user.html b/template/html/create_user.html
similarity index 100%
rename from server/template/html/create_user.html
rename to template/html/create_user.html
diff --git a/server/template/html/edit_category.html b/template/html/edit_category.html
similarity index 100%
rename from server/template/html/edit_category.html
rename to template/html/edit_category.html
diff --git a/server/template/html/edit_feed.html b/template/html/edit_feed.html
similarity index 100%
rename from server/template/html/edit_feed.html
rename to template/html/edit_feed.html
diff --git a/server/template/html/edit_user.html b/template/html/edit_user.html
similarity index 100%
rename from server/template/html/edit_user.html
rename to template/html/edit_user.html
diff --git a/server/template/html/entry.html b/template/html/entry.html
similarity index 100%
rename from server/template/html/entry.html
rename to template/html/entry.html
diff --git a/server/template/html/feed_entries.html b/template/html/feed_entries.html
similarity index 100%
rename from server/template/html/feed_entries.html
rename to template/html/feed_entries.html
diff --git a/server/template/html/feeds.html b/template/html/feeds.html
similarity index 100%
rename from server/template/html/feeds.html
rename to template/html/feeds.html
diff --git a/server/template/html/history.html b/template/html/history.html
similarity index 100%
rename from server/template/html/history.html
rename to template/html/history.html
diff --git a/server/template/html/import.html b/template/html/import.html
similarity index 100%
rename from server/template/html/import.html
rename to template/html/import.html
diff --git a/server/template/html/integrations.html b/template/html/integrations.html
similarity index 100%
rename from server/template/html/integrations.html
rename to template/html/integrations.html
diff --git a/server/template/html/login.html b/template/html/login.html
similarity index 100%
rename from server/template/html/login.html
rename to template/html/login.html
diff --git a/server/template/html/sessions.html b/template/html/sessions.html
similarity index 100%
rename from server/template/html/sessions.html
rename to template/html/sessions.html
diff --git a/server/template/html/settings.html b/template/html/settings.html
similarity index 100%
rename from server/template/html/settings.html
rename to template/html/settings.html
diff --git a/server/template/html/starred.html b/template/html/starred.html
similarity index 100%
rename from server/template/html/starred.html
rename to template/html/starred.html
diff --git a/server/template/html/unread.html b/template/html/unread.html
similarity index 100%
rename from server/template/html/unread.html
rename to template/html/unread.html
diff --git a/server/template/html/users.html b/template/html/users.html
similarity index 100%
rename from server/template/html/users.html
rename to template/html/users.html
diff --git a/server/template/template.go b/template/template.go
similarity index 97%
rename from server/template/template.go
rename to template/template.go
index a87d097ce2f311832a0c0feefc68a14317a472e0..60d4d854c561fccf021c2956eaffb0fe5007925b 100644
--- a/server/template/template.go
+++ b/template/template.go
@@ -15,10 +15,10 @@ import (
 	"github.com/miniflux/miniflux/config"
 	"github.com/miniflux/miniflux/duration"
 	"github.com/miniflux/miniflux/errors"
+	"github.com/miniflux/miniflux/filter"
+	"github.com/miniflux/miniflux/http/route"
 	"github.com/miniflux/miniflux/locale"
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/route"
-	"github.com/miniflux/miniflux/server/ui/filter"
 	"github.com/miniflux/miniflux/url"
 
 	"github.com/gorilla/mux"
diff --git a/server/template/views.go b/template/views.go
similarity index 99%
rename from server/template/views.go
rename to template/views.go
index 4eac6e44c269f3d1d81e75fc957df07c6750cfb6..8bc5c79259deb71c6dccbf75d0c7bbdd62a84f14 100644
--- a/server/template/views.go
+++ b/template/views.go
@@ -1,5 +1,5 @@
 // Code generated by go generate; DO NOT EDIT.
-// 2017-12-31 18:38:42.048775793 -0800 PST m=+0.029606951
+// 2018-01-02 21:59:10.091229271 -0800 PST m=+0.018604600
 
 package template
 
diff --git a/server/ui/controller/about.go b/ui/about.go
similarity index 75%
rename from server/ui/controller/about.go
rename to ui/about.go
index d6bfc279b7a452aa4bd68141e2ecabb0cbc6ed0f..91713d8f4809af89df4bd5f0c92ad02e8f2be75e 100644
--- a/server/ui/controller/about.go
+++ b/ui/about.go
@@ -2,15 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
-	"github.com/miniflux/miniflux/server/core"
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/version"
 )
 
 // AboutPage shows the about page.
-func (c *Controller) AboutPage(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) AboutPage(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		response.HTML().ServerError(err)
diff --git a/server/ui/controller/category.go b/ui/category.go
similarity index 82%
rename from server/ui/controller/category.go
rename to ui/category.go
index cf378c6d37413f46cc2be399b7bc78db705724a2..ba2d565a5894198b5dff5351bd6621a1bc61b11e 100644
--- a/server/ui/controller/category.go
+++ b/ui/category.go
@@ -2,19 +2,19 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"errors"
 
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/form"
+	"github.com/miniflux/miniflux/ui/form"
 )
 
 // ShowCategories shows the page with all categories.
-func (c *Controller) ShowCategories(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowCategories(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		response.HTML().ServerError(err)
@@ -36,7 +36,7 @@ func (c *Controller) ShowCategories(ctx *core.Context, request *core.Request, re
 }
 
 // ShowCategoryEntries shows all entries for the given category.
-func (c *Controller) ShowCategoryEntries(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowCategoryEntries(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	offset := request.QueryIntegerParam("offset", 0)
 
@@ -81,7 +81,7 @@ func (c *Controller) ShowCategoryEntries(ctx *core.Context, request *core.Reques
 }
 
 // CreateCategory shows the form to create a new category.
-func (c *Controller) CreateCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) CreateCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		response.HTML().ServerError(err)
@@ -94,7 +94,7 @@ func (c *Controller) CreateCategory(ctx *core.Context, request *core.Request, re
 }
 
 // SaveCategory validate and save the new category into the database.
-func (c *Controller) SaveCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) SaveCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
@@ -137,7 +137,7 @@ func (c *Controller) SaveCategory(ctx *core.Context, request *core.Request, resp
 }
 
 // EditCategory shows the form to modify a category.
-func (c *Controller) EditCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) EditCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	category, err := c.getCategoryFromURL(ctx, request, response)
@@ -156,7 +156,7 @@ func (c *Controller) EditCategory(ctx *core.Context, request *core.Request, resp
 }
 
 // UpdateCategory validate and update a category.
-func (c *Controller) UpdateCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	category, err := c.getCategoryFromURL(ctx, request, response)
@@ -199,7 +199,7 @@ func (c *Controller) UpdateCategory(ctx *core.Context, request *core.Request, re
 }
 
 // RemoveCategory delete a category from the database.
-func (c *Controller) RemoveCategory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RemoveCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	category, err := c.getCategoryFromURL(ctx, request, response)
@@ -215,7 +215,7 @@ func (c *Controller) RemoveCategory(ctx *core.Context, request *core.Request, re
 	response.Redirect(ctx.Route("categories"))
 }
 
-func (c *Controller) getCategoryFromURL(ctx *core.Context, request *core.Request, response *core.Response) (*model.Category, error) {
+func (c *Controller) getCategoryFromURL(ctx *handler.Context, request *handler.Request, response *handler.Response) (*model.Category, error) {
 	categoryID, err := request.IntegerParam("categoryID")
 	if err != nil {
 		response.HTML().BadRequest(err)
@@ -237,7 +237,7 @@ func (c *Controller) getCategoryFromURL(ctx *core.Context, request *core.Request
 	return category, nil
 }
 
-func (c *Controller) getCategoryFormTemplateArgs(ctx *core.Context, user *model.User, category *model.Category, categoryForm *form.CategoryForm) (tplParams, error) {
+func (c *Controller) getCategoryFormTemplateArgs(ctx *handler.Context, user *model.User, category *model.Category, categoryForm *form.CategoryForm) (tplParams, error) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		return nil, err
diff --git a/server/ui/controller/controller.go b/ui/controller.go
similarity index 91%
rename from server/ui/controller/controller.go
rename to ui/controller.go
index 8555c7b887eba1f5fab9db7310de8602b91cdf2f..44e0b2909c8bb4908430ba231d8b08fbc1f07a6b 100644
--- a/server/ui/controller/controller.go
+++ b/ui/controller.go
@@ -2,15 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/model"
 	"github.com/miniflux/miniflux/reader/feed"
 	"github.com/miniflux/miniflux/reader/opml"
 	"github.com/miniflux/miniflux/scheduler"
-	"github.com/miniflux/miniflux/server/core"
 	"github.com/miniflux/miniflux/storage"
 )
 
@@ -33,7 +33,7 @@ type Controller struct {
 	opmlHandler *opml.Handler
 }
 
-func (c *Controller) getCommonTemplateArgs(ctx *core.Context) (tplParams, error) {
+func (c *Controller) getCommonTemplateArgs(ctx *handler.Context) (tplParams, error) {
 	user := ctx.LoggedUser()
 	builder := c.store.NewEntryQueryBuilder(user.ID)
 	builder.WithStatus(model.EntryStatusUnread)
diff --git a/server/ui/controller/entry.go b/ui/entry.go
similarity index 91%
rename from server/ui/controller/entry.go
rename to ui/entry.go
index ca9f44af1dcce0a43b735b03c3d216afea1d5e0f..a67fa6837f1705fc69100ea1e873d2edd003c0c3 100644
--- a/server/ui/controller/entry.go
+++ b/ui/entry.go
@@ -2,24 +2,22 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"errors"
 
-	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/reader/sanitizer"
-
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/integration"
+	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
+	"github.com/miniflux/miniflux/reader/sanitizer"
 	"github.com/miniflux/miniflux/reader/scraper"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/payload"
 	"github.com/miniflux/miniflux/storage"
 )
 
 // FetchContent downloads the original HTML page and returns relevant contents.
-func (c *Controller) FetchContent(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) FetchContent(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	entryID, err := request.IntegerParam("entryID")
 	if err != nil {
 		response.HTML().BadRequest(err)
@@ -55,7 +53,7 @@ func (c *Controller) FetchContent(ctx *core.Context, request *core.Request, resp
 }
 
 // SaveEntry send the link to external services.
-func (c *Controller) SaveEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) SaveEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	entryID, err := request.IntegerParam("entryID")
 	if err != nil {
 		response.HTML().BadRequest(err)
@@ -92,7 +90,7 @@ func (c *Controller) SaveEntry(ctx *core.Context, request *core.Request, respons
 }
 
 // ShowFeedEntry shows a single feed entry in "feed" mode.
-func (c *Controller) ShowFeedEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowFeedEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	entryID, err := request.IntegerParam("entryID")
@@ -168,7 +166,7 @@ func (c *Controller) ShowFeedEntry(ctx *core.Context, request *core.Request, res
 }
 
 // ShowCategoryEntry shows a single feed entry in "category" mode.
-func (c *Controller) ShowCategoryEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowCategoryEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	categoryID, err := request.IntegerParam("categoryID")
@@ -244,7 +242,7 @@ func (c *Controller) ShowCategoryEntry(ctx *core.Context, request *core.Request,
 }
 
 // ShowUnreadEntry shows a single feed entry in "unread" mode.
-func (c *Controller) ShowUnreadEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowUnreadEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	entryID, err := request.IntegerParam("entryID")
@@ -314,7 +312,7 @@ func (c *Controller) ShowUnreadEntry(ctx *core.Context, request *core.Request, r
 }
 
 // ShowReadEntry shows a single feed entry in "history" mode.
-func (c *Controller) ShowReadEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowReadEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	entryID, err := request.IntegerParam("entryID")
@@ -374,7 +372,7 @@ func (c *Controller) ShowReadEntry(ctx *core.Context, request *core.Request, res
 }
 
 // ShowStarredEntry shows a single feed entry in "starred" mode.
-func (c *Controller) ShowStarredEntry(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowStarredEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	entryID, err := request.IntegerParam("entryID")
@@ -443,10 +441,10 @@ func (c *Controller) ShowStarredEntry(ctx *core.Context, request *core.Request,
 }
 
 // UpdateEntriesStatus handles Ajax request to update the status for a list of entries.
-func (c *Controller) UpdateEntriesStatus(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateEntriesStatus(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
-	entryIDs, status, err := payload.DecodeEntryStatusPayload(request.Body())
+	entryIDs, status, err := decodeEntryStatusPayload(request.Body())
 	if err != nil {
 		logger.Error("[Controller:UpdateEntryStatus] %v", err)
 		response.JSON().BadRequest(nil)
diff --git a/server/ui/controller/feed.go b/ui/feed.go
similarity index 81%
rename from server/ui/controller/feed.go
rename to ui/feed.go
index 7dfc56e57209ce2ec1dc12d3de7108d97413580d..e524edf902aed474faa13d47072c9c72131270b0 100644
--- a/server/ui/controller/feed.go
+++ b/ui/feed.go
@@ -2,19 +2,19 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"errors"
 
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/form"
+	"github.com/miniflux/miniflux/ui/form"
 )
 
 // RefreshAllFeeds refresh all feeds in the background for the current user.
-func (c *Controller) RefreshAllFeeds(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RefreshAllFeeds(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	jobs, err := c.store.NewUserBatch(user.ID, c.store.CountFeeds(user.ID))
 	if err != nil {
@@ -30,7 +30,7 @@ func (c *Controller) RefreshAllFeeds(ctx *core.Context, request *core.Request, r
 }
 
 // ShowFeedsPage shows the page with all subscriptions.
-func (c *Controller) ShowFeedsPage(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowFeedsPage(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	args, err := c.getCommonTemplateArgs(ctx)
@@ -53,7 +53,7 @@ func (c *Controller) ShowFeedsPage(ctx *core.Context, request *core.Request, res
 }
 
 // ShowFeedEntries shows all entries for the given feed.
-func (c *Controller) ShowFeedEntries(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowFeedEntries(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	offset := request.QueryIntegerParam("offset", 0)
 
@@ -98,7 +98,7 @@ func (c *Controller) ShowFeedEntries(ctx *core.Context, request *core.Request, r
 }
 
 // EditFeed shows the form to modify a subscription.
-func (c *Controller) EditFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) EditFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	feed, err := c.getFeedFromURL(request, response, user)
@@ -116,7 +116,7 @@ func (c *Controller) EditFeed(ctx *core.Context, request *core.Request, response
 }
 
 // UpdateFeed update a subscription and redirect to the feed entries page.
-func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	feed, err := c.getFeedFromURL(request, response, user)
@@ -151,7 +151,7 @@ func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, respon
 }
 
 // RemoveFeed delete a subscription from the database and redirect to the list of feeds page.
-func (c *Controller) RemoveFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RemoveFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
 		response.HTML().ServerError(err)
@@ -168,7 +168,7 @@ func (c *Controller) RemoveFeed(ctx *core.Context, request *core.Request, respon
 }
 
 // RefreshFeed refresh a subscription and redirect to the feed entries page.
-func (c *Controller) RefreshFeed(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RefreshFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
 		response.HTML().BadRequest(err)
@@ -183,7 +183,7 @@ func (c *Controller) RefreshFeed(ctx *core.Context, request *core.Request, respo
 	response.Redirect(ctx.Route("feedEntries", "feedID", feedID))
 }
 
-func (c *Controller) getFeedFromURL(request *core.Request, response *core.Response, user *model.User) (*model.Feed, error) {
+func (c *Controller) getFeedFromURL(request *handler.Request, response *handler.Response, user *model.User) (*model.Feed, error) {
 	feedID, err := request.IntegerParam("feedID")
 	if err != nil {
 		response.HTML().BadRequest(err)
@@ -204,7 +204,7 @@ func (c *Controller) getFeedFromURL(request *core.Request, response *core.Respon
 	return feed, nil
 }
 
-func (c *Controller) getFeedFormTemplateArgs(ctx *core.Context, user *model.User, feed *model.Feed, feedForm *form.FeedForm) (tplParams, error) {
+func (c *Controller) getFeedFormTemplateArgs(ctx *handler.Context, user *model.User, feed *model.Feed, feedForm *form.FeedForm) (tplParams, error) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		return nil, err
diff --git a/server/ui/form/auth.go b/ui/form/auth.go
similarity index 100%
rename from server/ui/form/auth.go
rename to ui/form/auth.go
diff --git a/server/ui/form/category.go b/ui/form/category.go
similarity index 100%
rename from server/ui/form/category.go
rename to ui/form/category.go
diff --git a/server/ui/form/feed.go b/ui/form/feed.go
similarity index 100%
rename from server/ui/form/feed.go
rename to ui/form/feed.go
diff --git a/server/ui/form/integration.go b/ui/form/integration.go
similarity index 100%
rename from server/ui/form/integration.go
rename to ui/form/integration.go
diff --git a/server/ui/form/settings.go b/ui/form/settings.go
similarity index 100%
rename from server/ui/form/settings.go
rename to ui/form/settings.go
diff --git a/server/ui/form/subscription.go b/ui/form/subscription.go
similarity index 100%
rename from server/ui/form/subscription.go
rename to ui/form/subscription.go
diff --git a/server/ui/form/user.go b/ui/form/user.go
similarity index 100%
rename from server/ui/form/user.go
rename to ui/form/user.go
diff --git a/server/ui/controller/history.go b/ui/history.go
similarity index 82%
rename from server/ui/controller/history.go
rename to ui/history.go
index 7347bacc41bf145c5ee76361970dbdcc15b1f598..63d0ca7884afc6e58c916b72f1a44dc9b72c90e8 100644
--- a/server/ui/controller/history.go
+++ b/ui/history.go
@@ -2,15 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // ShowHistoryPage renders the page with all read entries.
-func (c *Controller) ShowHistoryPage(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowHistoryPage(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	offset := request.QueryIntegerParam("offset", 0)
 
@@ -48,7 +48,7 @@ func (c *Controller) ShowHistoryPage(ctx *core.Context, request *core.Request, r
 }
 
 // FlushHistory changes all "read" items to "removed".
-func (c *Controller) FlushHistory(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) FlushHistory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	err := c.store.FlushHistory(user.ID)
diff --git a/server/ui/controller/icon.go b/ui/icon.go
similarity index 77%
rename from server/ui/controller/icon.go
rename to ui/icon.go
index f5ff1db3af962bfcb7baff4d0343cbc8367a2574..4c445f0426ee2f4de8b72f2543b6dcf7ad2009ee 100644
--- a/server/ui/controller/icon.go
+++ b/ui/icon.go
@@ -2,16 +2,16 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"time"
 
-	"github.com/miniflux/miniflux/server/core"
+	"github.com/miniflux/miniflux/http/handler"
 )
 
 // ShowIcon shows the feed icon.
-func (c *Controller) ShowIcon(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowIcon(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	iconID, err := request.IntegerParam("iconID")
 	if err != nil {
 		response.HTML().BadRequest(err)
diff --git a/server/ui/controller/integrations.go b/ui/integrations.go
similarity index 88%
rename from server/ui/controller/integrations.go
rename to ui/integrations.go
index 9ff4baa44424d16c72de786eac7df57d3cf470ed..b30185100e58e93de52684370af98adc96fa7894 100644
--- a/server/ui/controller/integrations.go
+++ b/ui/integrations.go
@@ -2,18 +2,18 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"crypto/md5"
 	"fmt"
 
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/form"
+	"github.com/miniflux/miniflux/http/handler"
+	"github.com/miniflux/miniflux/ui/form"
 )
 
 // ShowIntegrations renders the page with all external integrations.
-func (c *Controller) ShowIntegrations(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowIntegrations(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	integration, err := c.store.Integration(user.ID)
 	if err != nil {
@@ -51,7 +51,7 @@ func (c *Controller) ShowIntegrations(ctx *core.Context, request *core.Request,
 }
 
 // UpdateIntegration updates integration settings.
-func (c *Controller) UpdateIntegration(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateIntegration(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	integration, err := c.store.Integration(user.ID)
 	if err != nil {
diff --git a/server/ui/controller/login.go b/ui/login.go
similarity index 79%
rename from server/ui/controller/login.go
rename to ui/login.go
index ef99c82422c4755eb840c2b19839e5ed4775241b..daaac58c090a9afc1b15b9420aaf194d8f8c4c08 100644
--- a/server/ui/controller/login.go
+++ b/ui/login.go
@@ -2,19 +2,19 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/cookie"
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/cookie"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/form"
+	"github.com/miniflux/miniflux/ui/form"
 
 	"github.com/tomasen/realip"
 )
 
 // ShowLoginPage shows the login form.
-func (c *Controller) ShowLoginPage(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowLoginPage(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	if ctx.IsAuthenticated() {
 		response.Redirect(ctx.Route("unread"))
 		return
@@ -26,7 +26,7 @@ func (c *Controller) ShowLoginPage(ctx *core.Context, request *core.Request, res
 }
 
 // CheckLogin validates the username/password and redirects the user to the unread page.
-func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) CheckLogin(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	authForm := form.NewAuthForm(request.Request())
 	tplParams := tplParams{
 		"errorMessage": "Invalid username or password.",
@@ -64,7 +64,7 @@ func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, respon
 }
 
 // Logout destroy the session and redirects the user to the login page.
-func (c *Controller) Logout(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Logout(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	if err := c.store.RemoveUserSessionByToken(user.ID, ctx.UserSessionToken()); err != nil {
diff --git a/server/ui/controller/oauth2.go b/ui/oauth2.go
similarity index 90%
rename from server/ui/controller/oauth2.go
rename to ui/oauth2.go
index 2aaa5d7da1540e98a778020bd2c6d4eca03e948d..12ca57239f8697f5fbd336a948052b934b3648c9 100644
--- a/server/ui/controller/oauth2.go
+++ b/ui/oauth2.go
@@ -2,20 +2,20 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"github.com/miniflux/miniflux/config"
+	"github.com/miniflux/miniflux/http/cookie"
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/cookie"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/oauth2"
+	"github.com/miniflux/miniflux/oauth2"
 	"github.com/tomasen/realip"
 )
 
 // OAuth2Redirect redirects the user to the consent page to ask for permission.
-func (c *Controller) OAuth2Redirect(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) OAuth2Redirect(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	provider := request.StringParam("provider", "")
 	if provider == "" {
 		logger.Error("[OAuth2] Invalid or missing provider: %s", provider)
@@ -34,7 +34,7 @@ func (c *Controller) OAuth2Redirect(ctx *core.Context, request *core.Request, re
 }
 
 // OAuth2Callback receives the authorization code and create a new session.
-func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) OAuth2Callback(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	provider := request.StringParam("provider", "")
 	if provider == "" {
 		logger.Error("[OAuth2] Invalid or missing provider")
@@ -136,7 +136,7 @@ func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, re
 }
 
 // OAuth2Unlink unlink an account from the external provider.
-func (c *Controller) OAuth2Unlink(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) OAuth2Unlink(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	provider := request.StringParam("provider", "")
 	if provider == "" {
 		logger.Info("[OAuth2] Invalid or missing provider")
diff --git a/server/ui/controller/opml.go b/ui/opml.go
similarity index 78%
rename from server/ui/controller/opml.go
rename to ui/opml.go
index d8016779f9d666997d46878501af37cbf6d55097..80925fb52d07b54663355d792bffbd9a48ac20b8 100644
--- a/server/ui/controller/opml.go
+++ b/ui/opml.go
@@ -2,15 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // Export generates the OPML file.
-func (c *Controller) Export(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Export(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	opml, err := c.opmlHandler.Export(user.ID)
 	if err != nil {
@@ -22,7 +22,7 @@ func (c *Controller) Export(ctx *core.Context, request *core.Request, response *
 }
 
 // Import shows the import form.
-func (c *Controller) Import(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Import(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		response.HTML().ServerError(err)
@@ -35,7 +35,7 @@ func (c *Controller) Import(ctx *core.Context, request *core.Request, response *
 }
 
 // UploadOPML handles OPML file importation.
-func (c *Controller) UploadOPML(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UploadOPML(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	file, fileHeader, err := request.File("file")
 	if err != nil {
 		logger.Error("[Controller:UploadOPML] %v", err)
diff --git a/server/ui/controller/pagination.go b/ui/pagination.go
similarity index 97%
rename from server/ui/controller/pagination.go
rename to ui/pagination.go
index 1d61f74f13abbb2cc75f99ad92964bfd6e7f02c9..751ba8abc5c12088dbce8e1b826a8ec9ac899c21 100644
--- a/server/ui/controller/pagination.go
+++ b/ui/pagination.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 const (
 	nbItemsPerPage = 100
diff --git a/server/ui/payload/payload.go b/ui/payload.go
similarity index 80%
rename from server/ui/payload/payload.go
rename to ui/payload.go
index d91e34a85c6dbe06905bc1a1ce26a8d75231a123..28418288a7e259b5849d1c2b25766aebbce8cd9d 100644
--- a/server/ui/payload/payload.go
+++ b/ui/payload.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package payload
+package ui
 
 import (
 	"encoding/json"
@@ -12,8 +12,7 @@ import (
 	"github.com/miniflux/miniflux/model"
 )
 
-// DecodeEntryStatusPayload unserialize JSON request to update entry statuses.
-func DecodeEntryStatusPayload(data io.Reader) (entryIDs []int64, status string, err error) {
+func decodeEntryStatusPayload(data io.Reader) (entryIDs []int64, status string, err error) {
 	type payload struct {
 		EntryIDs []int64 `json:"entry_ids"`
 		Status   string  `json:"status"`
diff --git a/server/ui/controller/proxy.go b/ui/proxy.go
similarity index 88%
rename from server/ui/controller/proxy.go
rename to ui/proxy.go
index 6ee52b88757d3b87f9faf42503b62c958bb049ca..5237d006e1e10eb84a507da02edd38c4c944a46a 100644
--- a/server/ui/controller/proxy.go
+++ b/ui/proxy.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"encoding/base64"
@@ -12,12 +12,12 @@ import (
 
 	"github.com/miniflux/miniflux/crypto"
 	"github.com/miniflux/miniflux/http"
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // ImageProxy fetch an image from a remote server and sent it back to the browser.
-func (c *Controller) ImageProxy(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ImageProxy(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	// If we receive a "If-None-Match" header we assume the image in stored in browser cache
 	if request.Request().Header.Get("If-None-Match") != "" {
 		response.NotModified()
diff --git a/server/ui/controller/session.go b/ui/session.go
similarity index 79%
rename from server/ui/controller/session.go
rename to ui/session.go
index 05cb29edb66f90e612c03f08f845f6eafa63b512..4134ac61a33db5ba0cdb67001f29c5a97ac724a8 100644
--- a/server/ui/controller/session.go
+++ b/ui/session.go
@@ -2,15 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // ShowSessions shows the list of active user sessions.
-func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowSessions(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
@@ -32,7 +32,7 @@ func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, resp
 }
 
 // RemoveSession remove a user session.
-func (c *Controller) RemoveSession(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RemoveSession(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	sessionID, err := request.IntegerParam("sessionID")
diff --git a/server/ui/controller/settings.go b/ui/settings.go
similarity index 82%
rename from server/ui/controller/settings.go
rename to ui/settings.go
index feba8936c5bb835fcf5580ef5ad86652d1df7afa..725aa619873aaecb1bd771a4328f5151a39507ac 100644
--- a/server/ui/controller/settings.go
+++ b/ui/settings.go
@@ -2,18 +2,18 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/locale"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/form"
+	"github.com/miniflux/miniflux/ui/form"
 )
 
 // ShowSettings shows the settings page.
-func (c *Controller) ShowSettings(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowSettings(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	args, err := c.getSettingsFormTemplateArgs(ctx, user, nil)
@@ -26,7 +26,7 @@ func (c *Controller) ShowSettings(ctx *core.Context, request *core.Request, resp
 }
 
 // UpdateSettings update the settings.
-func (c *Controller) UpdateSettings(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateSettings(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	settingsForm := form.NewSettingsForm(request.Request())
@@ -66,7 +66,7 @@ func (c *Controller) UpdateSettings(ctx *core.Context, request *core.Request, re
 	response.Redirect(ctx.Route("settings"))
 }
 
-func (c *Controller) getSettingsFormTemplateArgs(ctx *core.Context, user *model.User, settingsForm *form.SettingsForm) (tplParams, error) {
+func (c *Controller) getSettingsFormTemplateArgs(ctx *handler.Context, user *model.User, settingsForm *form.SettingsForm) (tplParams, error) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		return args, err
diff --git a/server/ui/controller/starred.go b/ui/starred.go
similarity index 84%
rename from server/ui/controller/starred.go
rename to ui/starred.go
index e9da241a25705544eace1ebb801077dcb5831f20..738628f97f865e7c4c3c59428fbef5438d16d785 100644
--- a/server/ui/controller/starred.go
+++ b/ui/starred.go
@@ -2,16 +2,16 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // ShowStarredPage renders the page with all starred entries.
-func (c *Controller) ShowStarredPage(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowStarredPage(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	offset := request.QueryIntegerParam("offset", 0)
 
@@ -50,7 +50,7 @@ func (c *Controller) ShowStarredPage(ctx *core.Context, request *core.Request, r
 }
 
 // ToggleBookmark handles Ajax request to toggle bookmark value.
-func (c *Controller) ToggleBookmark(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ToggleBookmark(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	entryID, err := request.IntegerParam("entryID")
 	if err != nil {
diff --git a/server/ui/controller/static.go b/ui/static.go
similarity index 80%
rename from server/ui/controller/static.go
rename to ui/static.go
index 7cf7a35d0ca0e2d03f06f39f17cada9d124f62f6..266d9e691ee4e70a83e45c93c59f07e6a1be41c5 100644
--- a/server/ui/controller/static.go
+++ b/ui/static.go
@@ -2,19 +2,19 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"encoding/base64"
 	"time"
 
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/static"
+	"github.com/miniflux/miniflux/ui/static"
 )
 
 // Stylesheet renders the CSS.
-func (c *Controller) Stylesheet(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Stylesheet(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	stylesheet := request.StringParam("name", "white")
 	body := static.Stylesheets["common"]
 	etag := static.StylesheetsChecksums["common"]
@@ -28,12 +28,12 @@ func (c *Controller) Stylesheet(ctx *core.Context, request *core.Request, respon
 }
 
 // Javascript renders application client side code.
-func (c *Controller) Javascript(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Javascript(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	response.Cache("text/javascript; charset=utf-8", static.JavascriptChecksums["app"], []byte(static.Javascript["app"]), 48*time.Hour)
 }
 
 // Favicon renders the application favicon.
-func (c *Controller) Favicon(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Favicon(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	blob, err := base64.StdEncoding.DecodeString(static.Binaries["favicon.ico"])
 	if err != nil {
 		logger.Error("[Controller:Favicon] %v", err)
@@ -45,7 +45,7 @@ func (c *Controller) Favicon(ctx *core.Context, request *core.Request, response
 }
 
 // AppIcon returns application icons.
-func (c *Controller) AppIcon(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) AppIcon(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	filename := request.StringParam("filename", "favicon.png")
 	encodedBlob, found := static.Binaries[filename]
 	if !found {
@@ -65,7 +65,7 @@ func (c *Controller) AppIcon(ctx *core.Context, request *core.Request, response
 }
 
 // WebManifest renders web manifest file.
-func (c *Controller) WebManifest(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) WebManifest(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	type webManifestIcon struct {
 		Source string `json:"src"`
 		Sizes  string `json:"sizes"`
diff --git a/server/static/bin.go b/ui/static/bin.go
similarity index 99%
rename from server/static/bin.go
rename to ui/static/bin.go
index b464f3078fcf13825dce0f8f76046b3ddea5837f..11a953b31cb7bb1dc196e63ef2142170a5bfc2fa 100644
--- a/server/static/bin.go
+++ b/ui/static/bin.go
@@ -1,5 +1,5 @@
 // Code generated by go generate; DO NOT EDIT.
-// 2017-12-22 11:25:01.957187237 -0800 PST m=+0.022154999
+// 2018-01-02 21:59:10.082800492 -0800 PST m=+0.010175821
 
 package static
 
diff --git a/server/static/bin/favicon.ico b/ui/static/bin/favicon.ico
similarity index 100%
rename from server/static/bin/favicon.ico
rename to ui/static/bin/favicon.ico
diff --git a/server/static/bin/favicon.png b/ui/static/bin/favicon.png
similarity index 100%
rename from server/static/bin/favicon.png
rename to ui/static/bin/favicon.png
diff --git a/server/static/bin/touch-icon-ipad-retina.png b/ui/static/bin/touch-icon-ipad-retina.png
similarity index 100%
rename from server/static/bin/touch-icon-ipad-retina.png
rename to ui/static/bin/touch-icon-ipad-retina.png
diff --git a/server/static/bin/touch-icon-ipad.png b/ui/static/bin/touch-icon-ipad.png
similarity index 100%
rename from server/static/bin/touch-icon-ipad.png
rename to ui/static/bin/touch-icon-ipad.png
diff --git a/server/static/bin/touch-icon-iphone-retina.png b/ui/static/bin/touch-icon-iphone-retina.png
similarity index 100%
rename from server/static/bin/touch-icon-iphone-retina.png
rename to ui/static/bin/touch-icon-iphone-retina.png
diff --git a/server/static/bin/touch-icon-iphone.png b/ui/static/bin/touch-icon-iphone.png
similarity index 100%
rename from server/static/bin/touch-icon-iphone.png
rename to ui/static/bin/touch-icon-iphone.png
diff --git a/server/static/css.go b/ui/static/css.go
similarity index 99%
rename from server/static/css.go
rename to ui/static/css.go
index e7f678ff3024957e9b0a2931e5f0e3d93835d9f3..262b85352e88ffae58339cc17a078cf5c2bb606c 100644
--- a/server/static/css.go
+++ b/ui/static/css.go
@@ -1,5 +1,5 @@
 // Code generated by go generate; DO NOT EDIT.
-// 2017-12-28 18:55:07.393604426 -0800 PST m=+0.020325012
+// 2018-01-02 21:59:10.086272492 -0800 PST m=+0.013647821
 
 package static
 
diff --git a/server/static/css/black.css b/ui/static/css/black.css
similarity index 100%
rename from server/static/css/black.css
rename to ui/static/css/black.css
diff --git a/server/static/css/common.css b/ui/static/css/common.css
similarity index 100%
rename from server/static/css/common.css
rename to ui/static/css/common.css
diff --git a/server/static/js.go b/ui/static/js.go
similarity index 99%
rename from server/static/js.go
rename to ui/static/js.go
index ce9d070f9d68a7bcdd9eb123c68d887d72944361..04fc9458a24a2468229b71069440ff68f0dc964e 100644
--- a/server/static/js.go
+++ b/ui/static/js.go
@@ -1,5 +1,5 @@
 // Code generated by go generate; DO NOT EDIT.
-// 2017-12-28 18:55:07.395760341 -0800 PST m=+0.022480927
+// 2018-01-02 21:59:10.089270078 -0800 PST m=+0.016645407
 
 package static
 
diff --git a/server/static/js/app.js b/ui/static/js/app.js
similarity index 100%
rename from server/static/js/app.js
rename to ui/static/js/app.js
diff --git a/server/ui/controller/subscription.go b/ui/subscription.go
similarity index 84%
rename from server/ui/controller/subscription.go
rename to ui/subscription.go
index d243f9a9d2bd96c5d501a3e1633a772f7b8bd239..ad323ada98082cbd19ffc8c69edabfe18507e57f 100644
--- a/server/ui/controller/subscription.go
+++ b/ui/subscription.go
@@ -2,18 +2,18 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
 	"github.com/miniflux/miniflux/reader/subscription"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/form"
+	"github.com/miniflux/miniflux/ui/form"
 )
 
 // Bookmarklet prefill the form to add a subscription from the URL provided by the bookmarklet.
-func (c *Controller) Bookmarklet(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) Bookmarklet(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	args, err := c.getSubscriptionFormTemplateArgs(ctx, user)
 	if err != nil {
@@ -28,7 +28,7 @@ func (c *Controller) Bookmarklet(ctx *core.Context, request *core.Request, respo
 }
 
 // AddSubscription shows the form to add a new feed.
-func (c *Controller) AddSubscription(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) AddSubscription(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	args, err := c.getSubscriptionFormTemplateArgs(ctx, user)
@@ -41,7 +41,7 @@ func (c *Controller) AddSubscription(ctx *core.Context, request *core.Request, r
 }
 
 // SubmitSubscription try to find a feed from the URL provided by the user.
-func (c *Controller) SubmitSubscription(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) SubmitSubscription(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	args, err := c.getSubscriptionFormTemplateArgs(ctx, user)
@@ -98,7 +98,7 @@ func (c *Controller) SubmitSubscription(ctx *core.Context, request *core.Request
 }
 
 // ChooseSubscription shows a page to choose a subscription.
-func (c *Controller) ChooseSubscription(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ChooseSubscription(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	args, err := c.getSubscriptionFormTemplateArgs(ctx, user)
@@ -128,7 +128,7 @@ func (c *Controller) ChooseSubscription(ctx *core.Context, request *core.Request
 	response.Redirect(ctx.Route("feedEntries", "feedID", feed.ID))
 }
 
-func (c *Controller) getSubscriptionFormTemplateArgs(ctx *core.Context, user *model.User) (tplParams, error) {
+func (c *Controller) getSubscriptionFormTemplateArgs(ctx *handler.Context, user *model.User) (tplParams, error) {
 	args, err := c.getCommonTemplateArgs(ctx)
 	if err != nil {
 		return nil, err
diff --git a/server/ui/controller/unread.go b/ui/unread.go
similarity index 87%
rename from server/ui/controller/unread.go
rename to ui/unread.go
index 1dd7b07235f47a6872d18aeebc3dee7c46cc5529..75536ccc64549f11149926340993a520e0eb57a8 100644
--- a/server/ui/controller/unread.go
+++ b/ui/unread.go
@@ -2,15 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
 )
 
 // ShowUnreadPage render the page with all unread entries.
-func (c *Controller) ShowUnreadPage(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowUnreadPage(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	offset := request.QueryIntegerParam("offset", 0)
 
diff --git a/server/ui/controller/user.go b/ui/user.go
similarity index 84%
rename from server/ui/controller/user.go
rename to ui/user.go
index c5d4dba0704018df828a34da752a3b726f999465..caeb4bfdabe911e4f1e94920ccae8938508c6542 100644
--- a/server/ui/controller/user.go
+++ b/ui/user.go
@@ -2,19 +2,19 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package controller
+package ui
 
 import (
 	"errors"
 
+	"github.com/miniflux/miniflux/http/handler"
 	"github.com/miniflux/miniflux/logger"
 	"github.com/miniflux/miniflux/model"
-	"github.com/miniflux/miniflux/server/core"
-	"github.com/miniflux/miniflux/server/ui/form"
+	"github.com/miniflux/miniflux/ui/form"
 )
 
 // ShowUsers shows the list of users.
-func (c *Controller) ShowUsers(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) ShowUsers(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	if !user.IsAdmin {
@@ -41,7 +41,7 @@ func (c *Controller) ShowUsers(ctx *core.Context, request *core.Request, respons
 }
 
 // CreateUser shows the user creation form.
-func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) CreateUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	if !user.IsAdmin {
@@ -62,7 +62,7 @@ func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, respon
 }
 
 // SaveUser validate and save the new user into the database.
-func (c *Controller) SaveUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) SaveUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	if !user.IsAdmin {
@@ -110,7 +110,7 @@ func (c *Controller) SaveUser(ctx *core.Context, request *core.Request, response
 }
 
 // EditUser shows the form to edit a user.
-func (c *Controller) EditUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) EditUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	if !user.IsAdmin {
@@ -140,7 +140,7 @@ func (c *Controller) EditUser(ctx *core.Context, request *core.Request, response
 }
 
 // UpdateUser validate and update a user.
-func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) UpdateUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 
 	if !user.IsAdmin {
@@ -196,7 +196,7 @@ func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, respon
 }
 
 // RemoveUser deletes a user from the database.
-func (c *Controller) RemoveUser(ctx *core.Context, request *core.Request, response *core.Response) {
+func (c *Controller) RemoveUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
 	user := ctx.LoggedUser()
 	if !user.IsAdmin {
 		response.HTML().Forbidden()
@@ -216,7 +216,7 @@ func (c *Controller) RemoveUser(ctx *core.Context, request *core.Request, respon
 	response.Redirect(ctx.Route("users"))
 }
 
-func (c *Controller) getUserFromURL(ctx *core.Context, request *core.Request, response *core.Response) (*model.User, error) {
+func (c *Controller) getUserFromURL(ctx *handler.Context, request *handler.Request, response *handler.Response) (*model.User, error) {
 	userID, err := request.IntegerParam("userID")
 	if err != nil {
 		response.HTML().BadRequest(err)