diff --git a/server/app/__init__.py b/server/app/__init__.py
index 8b9293e319c9e6fec0788ac9a9dad2bdcf82f571..7b5a8add00813f9d44c3ae1db7f21d9910e63fd6 100644
--- a/server/app/__init__.py
+++ b/server/app/__init__.py
@@ -1,20 +1,13 @@
-from flask import Flask
-from flask_bcrypt import Bcrypt
-from flask_jwt_extended.jwt_manager import JWTManager
-from flask_sqlalchemy import SQLAlchemy
+from flask import Flask, redirect, request
 
-from app.database import Base
-
-bcrypt = Bcrypt()
-jwt = JWTManager()
-db = SQLAlchemy(model_class=Base)
-
-from app.database import models
+import app.core.models as models
+from app.core import bcrypt, db, jwt
 
 
 def create_app(config_name="configmodule.DevelopmentConfig"):
     app = Flask(__name__)
     app.config.from_object(config_name)
+    app.url_map.strict_slashes = False
 
     with app.app_context():
 
@@ -22,9 +15,16 @@ def create_app(config_name="configmodule.DevelopmentConfig"):
         jwt.init_app(app)
         db.init_app(app)
 
-        from app.api import api_blueprint
+        from app.apis import flask_api
+
+        flask_api.init_app(app)
+
+        @app.before_request
+        def clear_trailing():
+            rp = request.path
+            if rp != "/" and rp.endswith("/"):
+                return redirect(rp[:-1])
 
-        app.register_blueprint(api_blueprint, url_prefix="/api")
         return app
 
 
diff --git a/server/app/api/competitions.py b/server/app/api/competitions.py
deleted file mode 100644
index f89b20b9b2d5e49a22014c4b948a202947ce7dd3..0000000000000000000000000000000000000000
--- a/server/app/api/competitions.py
+++ /dev/null
@@ -1,46 +0,0 @@
-###
-# Admin stuff placed here for later use
-# No need to implement this before the application is somewhat done
-###
-
-import app.database.controller as dbc
-import app.utils.http_codes as codes
-from app import db
-from app.api import admin_required, api_blueprint, object_response, query_response, text_response
-from app.database.models import Blacklist, City, Competition, Role, User
-from app.utils.validator import edit_user_schema, login_schema, register_schema, validate_object
-from flask import request
-from flask_jwt_extended import (
-    create_access_token,
-    create_refresh_token,
-    get_jwt_identity,
-    get_raw_jwt,
-    jwt_refresh_token_required,
-    jwt_required,
-)
-
-
-@api_blueprint.route("/competitions/<int:id>", methods=["GET"])
-@jwt_required
-def get(id):
-    item = Competition.query.filter(Competition.id == id).first()
-    if not item:
-        return text_response("Competition not found", codes.NOT_FOUND)
-
-    return query_response(item)
-
-
-@api_blueprint.route("/competitions", methods=["POST"])
-@jwt_required
-def create():
-    json_dict = request.get_json(force=True)
-
-    name = json_dict.get("name")
-    city_id = json_dict.get("city_id")
-    year = json_dict.get("year", 2020)
-    style_id = json_dict.get("style_id", 0)
-
-    dbc.add.competition(name, year, style_id, city_id)
-
-    item = Competition.query.filter(Competition.name == name).first()
-    return query_response(item)
diff --git a/server/app/api/users.py b/server/app/api/users.py
deleted file mode 100644
index 6b0e3984b330e7e7a16d34b3b68769b79238c8ef..0000000000000000000000000000000000000000
--- a/server/app/api/users.py
+++ /dev/null
@@ -1,211 +0,0 @@
-import datetime
-
-import app.database.controller as dbc
-import app.utils.http_codes as codes
-from app import db
-from app.api import admin_required, api_blueprint, object_response, query_response, text_response
-from app.database.models import Blacklist, City, Role, User
-from app.utils.validator import edit_user_schema, login_schema, register_schema, validate_object
-from flask import request
-from flask_jwt_extended import (
-    create_access_token,
-    create_refresh_token,
-    get_jwt_identity,
-    get_raw_jwt,
-    jwt_refresh_token_required,
-    jwt_required,
-)
-
-
-##Helpers
-def _get_current_user():
-    return User.query.filter_by(id=get_jwt_identity()).first()
-
-
-def _create_token(user):
-    expires = datetime.timedelta(days=7)
-    claims = {"role": user.role.name}
-
-    return create_access_token(identity=user.id, expires_delta=expires, user_claims=claims)
-
-
-@api_blueprint.route("/users/test", methods=["GET"])
-def test():
-    return text_response("hello teknik8")
-
-
-@api_blueprint.route("/users/test_auth", methods=["GET"])
-@jwt_required
-@admin_required()
-def test_auth():
-    return text_response("you are authenticated")
-
-
-@api_blueprint.route("/roles", methods=["GET"])
-@jwt_required
-def get_roles():
-    return query_response(Role.query.all())
-
-
-@api_blueprint.route("/users/login", methods=["POST"])
-def login():
-    json_dict = request.get_json(force=True)
-
-    validate_msg = validate_object(login_schema, json_dict)
-    if validate_msg is not None:
-        return text_response(validate_msg, codes.BAD_REQUEST)
-
-    email = json_dict.get("email")
-    password = json_dict.get("password")
-    user = User.query.filter_by(email=email).first()
-
-    if not user or not user.is_correct_password(password):
-        return text_response("Invalid email or password", codes.UNAUTHORIZED)
-
-    access_token = _create_token(user)
-    refresh_token = create_refresh_token(identity=user.id)
-
-    response = {"id": user.id, "access_token": access_token, "refresh_token": refresh_token}
-    return object_response(response)
-
-
-@api_blueprint.route("/users/logout", methods=["POST"])
-@jwt_required
-def logout():
-    jti = get_raw_jwt()["jti"]
-
-    db.session.add(Blacklist(jti))
-    db.session.commit()
-    return text_response("Logged out")
-
-
-@api_blueprint.route("/users/refresh", methods=["POST"])
-@jwt_refresh_token_required
-def refresh():
-    current_user = get_jwt_identity()
-    response = {"access_token": _create_token(current_user)}
-
-    return object_response(response)
-
-
-@api_blueprint.route("/users/", methods=["POST"])
-def create():
-    json_dict = request.get_json(force=True)
-
-    validate_msg = validate_object(register_schema, json_dict)
-    if validate_msg is not None:
-        return text_response(validate_msg, codes.BAD_REQUEST)
-
-    email = json_dict.get("email")
-    password = json_dict.get("password")
-    role = json_dict.get("role")
-    city = json_dict.get("city")
-
-    existing_user = User.query.filter(User.email == email).first()
-
-    if existing_user is not None:
-        return text_response("User already exists", codes.BAD_REQUEST)
-
-    dbc.add.user(email, password, role, city)
-
-    item_user = User.query.filter(User.email == email).first()
-
-    return query_response(item_user)
-
-
-@api_blueprint.route("/users/", defaults={"user_id": None}, methods=["PUT"])
-@api_blueprint.route("/users/<int:user_id>", methods=["PUT"])
-@jwt_required
-def edit(user_id):
-    json_dict = request.get_json(force=True)
-
-    validate_msg = validate_object(edit_user_schema, json_dict)
-    if validate_msg is not None:
-        return text_response(validate_msg, codes.BAD_REQUEST)
-
-    if user_id:
-        item_user = User.query.filter(User.id == user_id).first()
-    else:
-        item_user = _get_current_user()
-
-    name = json_dict.get("name")
-    role = json_dict.get("city")
-    city = json_dict.get("password")
-
-    if name:
-        item_user.name = name.title()
-
-    if city:
-        if City.query.filter(City.name == city).first() is None:
-            return text_response(f"City {city} does not exist", codes.BAD_REQUEST)
-        item_user.city = city
-
-    if role:
-        if Role.query.filter(Role.name == role).first() is None:
-            return text_response(f"Role {role} does not exist", codes.BAD_REQUEST)
-        item_user.role = role
-
-    db.session.commit()
-    db.session.refresh(item_user)
-    return query_response(item_user)
-
-
-@api_blueprint.route("/users/", defaults={"user_id": None}, methods=["DELETE"])
-@api_blueprint.route("/users/<int:user_id>", methods=["DELETE"])
-@jwt_required
-def delete(user_id):
-    if user_id:
-        item_user = User.query.filter(User.id == user_id).first()
-    else:
-        item_user = _get_current_user()
-
-    db.session.delete(item_user)
-    jti = get_raw_jwt()["jti"]
-    db.session.add(Blacklist(jti))
-    db.session.commit()
-
-    return text_response("User deleted")
-
-
-###
-# Getters
-###
-@api_blueprint.route("/users/", defaults={"user_id": None}, methods=["GET"])
-@api_blueprint.route("/users/<int:user_id>", methods=["GET"])
-@jwt_required
-def get(user_id):
-
-    if user_id:
-        user = User.query.filter(User.id == user_id).first()
-    else:
-        user = _get_current_user()
-
-    if not user:
-        return text_response("User not found", codes.NOT_FOUND)
-
-    return query_response(user)
-
-
-# Searchable, returns 15 max at default
-@api_blueprint.route("/users/search", methods=["GET"])
-@jwt_required
-def search():
-    arguments = request.args
-    query = User.query
-
-    if "name" in arguments:
-        query = query.filter(User.name.like(f"%{arguments['name']}%"))
-
-    if "email" in arguments:
-        query = query.filter(User.email.like(f"%{arguments['email']}%"))
-
-    if "page" in arguments:
-        page_size = 15
-        if "page_size" in arguments:
-            page_size = int(arguments["page_size"])
-        query = query.limit(page_size)
-        query = query.offset(int(arguments["page"]) * page_size)
-    else:
-        query = query.limit(15)
-
-    return query_response(query.all())
diff --git a/server/app/api/__init__.py b/server/app/apis/__init__.py
similarity index 70%
rename from server/app/api/__init__.py
rename to server/app/apis/__init__.py
index 09ac41cf450eec1a478a395c7a27003a3b383a91..dbff66c786e573fa5acb9c8a63ce187e344f745d 100644
--- a/server/app/api/__init__.py
+++ b/server/app/apis/__init__.py
@@ -1,6 +1,5 @@
 from functools import wraps
 
-from flask import Blueprint
 from flask_jwt_extended import verify_jwt_in_request
 from flask_jwt_extended.utils import get_jwt_claims
 
@@ -37,7 +36,15 @@ def object_response(items, code=200):
     return {"result": items}, code
 
 
-api_blueprint = Blueprint("api", __name__)
+from flask_restx import Api
 
-# Import the rest of the routes.
-from app.api import admin, users
+from .auth import api as ns2
+from .competitions import api as ns4
+from .slides import api as ns3
+from .users import api as ns1
+
+flask_api = Api()
+flask_api.add_namespace(ns1, path="/api/users")
+flask_api.add_namespace(ns3, path="/api/slides")
+flask_api.add_namespace(ns2, path="/api/auth")
+flask_api.add_namespace(ns4, path="/api/competitions")
diff --git a/server/app/api/admin.py b/server/app/apis/admin.py
similarity index 100%
rename from server/app/api/admin.py
rename to server/app/apis/admin.py
diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..06706127ccf9694d28ce71d880042a6f78782df7
--- /dev/null
+++ b/server/app/apis/auth.py
@@ -0,0 +1,94 @@
+import app.core.controller as dbc
+import app.core.utils.http_codes as codes
+from app.apis import admin_required
+from app.core.models import User
+from app.core.parsers import create_user_parser, login_parser
+from flask_jwt_extended import (
+    create_access_token,
+    create_refresh_token,
+    get_jwt_identity,
+    get_raw_jwt,
+    jwt_refresh_token_required,
+    jwt_required,
+)
+from flask_restx import Namespace, Resource
+
+api = Namespace("auth")
+
+
+def get_user_claims(item_user):
+    return {"role": item_user.role.name, "city": item_user.city.name}
+
+
+@api.route("/signup")
+class AuthSignup(Resource):
+    @jwt_required
+    def post(self):
+        args = create_user_parser.parse_args(strict=True)
+        email = args.get("email")
+        password = args.get("password")
+        role = args.get("role")
+        city = args.get("city")
+
+        if User.query.filter(User.email == email).count() > 0:
+            api.abort(codes.BAD_REQUEST, "User already exists")
+
+        item_user = dbc.add.user(email, password, role, city)
+        if not item_user:
+            api.abort(codes.BAD_REQUEST, "User could not be created")
+
+        return {"id": item_user.id}
+
+
+@api.route("/delete/<ID>")
+@api.param("ID")
+class AuthDelete(Resource):
+    @jwt_required
+    def delete(self, ID):
+        item_user = User.query.filter(User.id == ID).first()
+        dbc.delete(item_user)
+        if ID == get_jwt_identity():
+            jti = get_raw_jwt()["jti"]
+            dbc.add.blacklist(jti)
+        return "deleted"
+
+
+@api.route("/login")
+class AuthLogin(Resource):
+    def post(self):
+        args = login_parser.parse_args(strict=True)
+        email = args.get("email")
+        password = args.get("password")
+        item_user = User.query.filter_by(email=email).first()
+
+        if not item_user or not item_user.is_correct_password(password):
+            api.abort(codes.UNAUTHORIZED, "Invalid email or password")
+
+        access_token = create_access_token(item_user.id, user_claims=get_user_claims(item_user))
+        refresh_token = create_refresh_token(item_user.id)
+
+        response = {"id": item_user.id, "access_token": access_token, "refresh_token": refresh_token}
+        return response
+
+
+@api.route("/logout")
+class AuthLogout(Resource):
+    @jwt_required
+    def post(self):
+        jti = get_raw_jwt()["jti"]
+        dbc.add.blacklist(jti)
+        return "logout"
+
+
+@api.route("/refresh")
+class AuthRefresh(Resource):
+    @jwt_required
+    @jwt_refresh_token_required
+    def post(self):
+        old_jti = get_raw_jwt()["jti"]
+
+        item_user = User.query.filter_by(id=get_jwt_identity()).first()
+        access_token = create_access_token(item_user.id, user_claims=get_user_claims(item_user))
+        dbc.add.blacklist(old_jti)
+        response = {"access_token": access_token}
+        return response
diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py
new file mode 100644
index 0000000000000000000000000000000000000000..46c0e88f99f6976d49817be0a7dd6eb36ef01385
--- /dev/null
+++ b/server/app/apis/competitions.py
@@ -0,0 +1,75 @@
+import app.core.controller as dbc
+import app.core.utils.http_codes as codes
+from app.apis import admin_required
+from app.core.models import Competition, User
+from app.core.parsers import competition_parser, competition_search_parser
+from app.core.schemas import competition_schema
+from flask_jwt_extended import get_jwt_identity, jwt_required
+from flask_restx import Namespace, Resource
+
+api = Namespace("competitions")
+competition_model = api.model(*competition_schema)
+
+
+@api.route("/")
+class CompetitionBase(Resource):
+    @jwt_required
+    @api.marshal_with(competition_model)
+    def post(self):
+        args = competition_parser.parse_args(strict=True)
+
+        name = args.get("name")
+        city_id = args.get("city_id")
+        year = args.get("year")
+        style_id = args.get("style_id")
+
+        # Add competition
+        item_competition = dbc.add.competition(name, year, style_id, city_id)
+
+        # Add default slide
+        item_slide = dbc.add.slide(item_competition.id)
+
+        return item_competition
+
+
+@api.route("/<ID>")
+@api.param("ID")
+class CompetitionByID(Resource):
+    @jwt_required
+    @api.marshal_with(competition_model)
+    def get(self, ID):
+        item = Competition.query.filter(Competition.id == ID).first()
+        return item
+
+    @jwt_required
+    @api.marshal_with(competition_model)
+    def put(self, ID):
+        args = competition_parser.parse_args(strict=True)
+
+        item = Competition.query.filter(Competition.id == ID).first()
+        name = args.get("name")
+        year = args.get("year")
+        city_id = args.get("city_id")
+        style_id = args.get("style_id")
+        return dbc.edit.competition(item, name, year, city_id, style_id)
+
+    @jwt_required
+    def delete(self, ID):
+        item = Competition.query.filter(Competition.id == ID).first()
+        dbc.delete(item)
+        return "deleted"
+
+
+@api.route("/search")
+class CompetitionSearch(Resource):
+    @jwt_required
+    @api.marshal_with(competition_model)
+    def get(self):
+        args = competition_search_parser.parse_args(strict=True)
+        name = args.get("name")
+        year = args.get("year")
+        city_id = args.get("city_id")
+        style_id = args.get("style_id")
+        page = args.get("page")
+        page_size = args.get("page_size")
+        return dbc.get.search_competitions(name, year, city_id, style_id, page, page_size)
diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc6dfa3d748a569d154a2179e4cdf25b7b9416ed
--- /dev/null
+++ b/server/app/apis/slides.py
@@ -0,0 +1,3 @@
+from flask_restx import Namespace, Resource, fields
+
+api = Namespace("slides")
diff --git a/server/app/apis/users.py b/server/app/apis/users.py
new file mode 100644
index 0000000000000000000000000000000000000000..424e608e824ec7202580aaba32b1e1d0451f4879
--- /dev/null
+++ b/server/app/apis/users.py
@@ -0,0 +1,71 @@
+import app.core.controller as dbc
+import app.core.utils.http_codes as codes
+from app.apis import admin_required
+from app.core.models import User
+from app.core.parsers import user_parser, user_search_parser
+from app.core.schemas import user_schema
+from flask_jwt_extended import get_jwt_identity, jwt_required
+from flask_restx import Namespace, Resource
+
+api = Namespace("users")
+user_model = api.model(*user_schema)
+
+
+def edit_user(item_user, args):
+    email = args.get("email")
+    name = args.get("name")
+    city = args.get("city")
+    role = args.get("role")
+
+    if email:
+        if User.query.filter(User.email == args["email"]).count() > 0:
+            api.abort(codes.BAD_REQUEST, "Email is already in use")
+
+    return dbc.edit.user(item_user, name, email, city, role)
+
+
+@api.route("/")
+class UserBase(Resource):
+    @jwt_required
+    @api.marshal_list_with(user_model)
+    def get(self):
+        return User.query.filter(User.id == get_jwt_identity()).first()
+
+    @jwt_required
+    @api.marshal_with(user_model)
+    def put(self):
+        args = user_parser.parse_args(strict=True)
+        item_user = User.query.filter(User.id == get_jwt_identity()).first()
+        return edit_user(item_user, args)
+
+
+@api.route("/<ID>")
+@api.param("ID")
+class UserByID(Resource):
+    @jwt_required
+    @api.marshal_with(user_model)
+    def get(self, ID):
+        return User.query.filter(User.id == ID).first()
+
+    @jwt_required
+    @api.marshal_with(user_model)
+    def put(self, ID):
+        args = user_parser.parse_args(strict=True)
+        item_user = User.query.filter(User.id == ID).first()
+        return edit_user(item_user, args)
+
+
+@api.route("/search")
+class UserSearch(Resource):
+    @jwt_required
+    @api.marshal_list_with(user_model)
+    def get(self):
+        args = user_search_parser.parse_args(strict=True)
+        name = args.get("name")
+        email = args.get("email")
+        role = args.get("role")
+        city = args.get("city")
+        page = args.get("page", 1)
+        page_size = args.get("page_size", 15)
+
+        return dbc.get.search_user(email, name, city, role, page, page_size)
diff --git a/server/app/database/__init__.py b/server/app/core/__init__.py
similarity index 59%
rename from server/app/database/__init__.py
rename to server/app/core/__init__.py
index c2b33c83f718b98c97bc7f01b2f26a998c497e9b..1f6cad4846d15460bd6c6e113297c4aa319dbb63 100644
--- a/server/app/database/__init__.py
+++ b/server/app/core/__init__.py
@@ -1,4 +1,7 @@
 import sqlalchemy as sa
+from flask_bcrypt import Bcrypt
+from flask_jwt_extended.jwt_manager import JWTManager
+from flask_sqlalchemy import SQLAlchemy
 from flask_sqlalchemy.model import Model
 from sqlalchemy.sql import func
 
@@ -7,3 +10,8 @@ class Base(Model):
     __abstract__ = True
     _created = sa.Column(sa.DateTime(timezone=True), server_default=func.now())
     _updated = sa.Column(sa.DateTime(timezone=True), onupdate=func.now())
+
+
+db = SQLAlchemy(model_class=Base)
+bcrypt = Bcrypt()
+jwt = JWTManager()
diff --git a/server/app/core/controller/__init__.py b/server/app/core/controller/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..05f51a94602e790ffc2034f0807218f806fb6709
--- /dev/null
+++ b/server/app/core/controller/__init__.py
@@ -0,0 +1,8 @@
+# import add, get
+from app.core import db
+from app.core.controller import add, edit, get
+
+
+def delete(item):
+    db.session.delete(item)
+    db.session.commit()
diff --git a/server/app/database/controller/add.py b/server/app/core/controller/add.py
similarity index 78%
rename from server/app/database/controller/add.py
rename to server/app/core/controller/add.py
index 5cc7a0b7ea666f60d2e6b91a704e14658a6f6c12..48f9c949238f8a175590d876f9499c12b22b5087 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/core/controller/add.py
@@ -1,5 +1,10 @@
-from app import db
-from app.database.models import City, Competition, Role, Slide, Style, User
+from app.core import db
+from app.core.models import Blacklist, City, Competition, Role, Slide, User
+
+
+def blacklist(jti):
+    db.session.add(Blacklist(jti))
+    db.session.commit()
 
 
 def user(email, plaintext_password, role, city):
@@ -27,5 +32,5 @@ def slide(competition_id):
     db.session.add(Slide(order, competition_id))
     db.session.commit()
 
-    filters = (Slide.order == order) & (Competition.competition_id == competition_id)
+    filters = (Slide.order == order) & (Slide.competition_id == competition_id)
     return Slide.query.filter(filters).first()
diff --git a/server/app/core/controller/edit.py b/server/app/core/controller/edit.py
new file mode 100644
index 0000000000000000000000000000000000000000..da5340812343310a5e92b3205db804157eb10393
--- /dev/null
+++ b/server/app/core/controller/edit.py
@@ -0,0 +1,38 @@
+from app.core import db
+from app.core.models import Blacklist, City, Competition, Role, Slide, User
+
+
+def competition(item, name=None, year=None, city_id=None, style_id=None):
+    if name:
+        item.name = name
+    if year:
+        item.year = year
+    if city_id:
+        item.city_id = city_id
+    if style_id:
+        item.style_id = style_id
+
+    db.session.commit()
+    db.session.refresh(item)
+    return item
+
+
+def user(item, name=None, email=None, city=None, role=None):
+
+    if name:
+        item.name = name.title()
+
+    if email:
+        item.email = email
+
+    if city:
+        item_city = City.query.filter(City.name == city).first()
+        item.city_id = item_city.id
+
+    if role:
+        item_role = Role.query.filter(Role.name == role).first()
+        item.role_id = item_role.id
+
+    db.session.commit()
+    db.session.refresh(item)
+    return item
diff --git a/server/app/core/controller/get.py b/server/app/core/controller/get.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f4fae14078a9d5ac6d0e1e9b44a61ad492553a7
--- /dev/null
+++ b/server/app/core/controller/get.py
@@ -0,0 +1,33 @@
+from app.core.models import Competition, User
+
+
+def search_user(email=None, name=None, city=None, role=None, page=1, page_size=15):
+    query = User.query
+    if name:
+        query = query.filter(User.name.like(f"%{name}%"))
+    if email:
+        query = query.filter(User.email.like(f"%{email}%"))
+    if city:
+        query = query.filter(User.city.name == city)
+    if role:
+        query = query.filter(User.role.name == role)
+
+    query = query.limit(page_size).offset(page * page_size)
+
+    return query.all()
+
+
+def search_competitions(name=None, year=None, city_id=None, style_id=None, page=1, page_size=15):
+    query = Competition.query
+    if name:
+        query = query.filter(Competition.name.like(f"%{name}%"))
+    if year:
+        query = query.filter(Competition.year == year)
+    if city_id:
+        query = query.filter(Competition.city_id == city_id)
+    if style_id:
+        query = query.filter(Competition.style_id == style_id)
+
+    query = query.limit(page_size).offset(page * page_size)
+
+    return query.all()
diff --git a/server/app/database/models.py b/server/app/core/models.py
similarity index 80%
rename from server/app/database/models.py
rename to server/app/core/models.py
index 62e07094cf1d85d11abb9fc34c52e0dae48b0b58..520887e39780809ecf8e325ba27c008a51bf0c95 100644
--- a/server/app/database/models.py
+++ b/server/app/core/models.py
@@ -1,4 +1,4 @@
-from app import bcrypt, db
+from app.core import bcrypt, db
 from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
 
 STRING_SIZE = 254
@@ -72,7 +72,7 @@ class User(db.Model):
             "role_id": self.role_id,
             "city_id": self.city_id,
         }
-
+    
     @hybrid_property
     def password(self):
         return self._password
@@ -227,60 +227,3 @@ class QuestionType(db.Model):
     def __init__(self, name):
         self.name = name
 
-
-"""
-QuestionHandler = db.Table(
-    "question_handler",
-    db.Column("question_id", db.Integer, db.ForeignKey("question.id"), primary_key=True),
-    db.Column("sub_question_id", db.Integer, unique=True),
-    db.Column("question_type", db.Integer, nullable=False),
-)
-
-class TrueFalseQuestion(db.Model):
-    id = db.Column(db.Integer, primary_key=True)
-    true_false = db.Column(db.Boolean, nullable=False, default=False)
-
-    def __init__(self, true_false):
-        self.true_false = true_false
-
-
-class TextQuestion(db.Model):
-    id = db.Column(db.Integer, primary_key=True)
-    alternatives = db.relationship("TextQuestionAlternative", backref="text_question")
-
-
-class TextQuestionAlternative(db.Model):
-    id = db.Column(db.Integer, primary_key=True)
-    text = db.Column(db.String(STRING_SIZE), nullable=False)
-    text_question_id = db.Column(db.Integer, db.ForeignKey("text_question.id"), nullable=False)
-
-    def __init__(self, text, text_question_id):
-        self.text = text
-        self.text_question_id = text_question_id
-
-
-class MCQuestion(db.Model):
-    id = db.Column(db.Integer, primary_key=True)
-    title = db.Column(db.String(STRING_SIZE), nullable=False)
-    timer = db.Column(db.Integer, nullable=False)
-    alternatives = db.relationship("MCQuestionAlternative", backref="mc_question")
-
-    def __init__(self, title, timer):
-        self.title = title
-        self.timer = timer
-
-
-class MCQuestionAlternative(db.Model):
-    id = db.Column(db.Integer, primary_key=True)
-    text = db.Column(db.String(STRING_SIZE), nullable=False)
-    true_false = db.Column(db.Boolean, nullable=False, default=False)
-    mc_id = db.Column(db.Integer, db.ForeignKey("mc_question.id"), nullable=False)
-
-    def __init__(self, text, true_false, mc_id):
-        self.text = text
-        self.true_false = true_false
-        self.mc_id = mc_id
-
-
-
-"""
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c354d9079da45fd9187968003d06e5e9ebb9d35
--- /dev/null
+++ b/server/app/core/parsers.py
@@ -0,0 +1,47 @@
+from flask_restx import inputs, reqparse
+
+###SEARCH####
+search_parser = reqparse.RequestParser()
+search_parser.add_argument("page", type=int, default=0, location="args")
+search_parser.add_argument("page_size", type=int, default=15, location="args")
+
+###LOGIN####
+login_parser = reqparse.RequestParser()
+login_parser.add_argument("email", type=inputs.email(), required=True, location="json")
+login_parser.add_argument("password", required=True, location="json")
+
+###CREATE_USER####
+create_user_parser = login_parser.copy()
+create_user_parser.add_argument("email", type=inputs.email(), required=True, location="json")
+create_user_parser.add_argument("city", required=True, location="json")
+create_user_parser.add_argument("role", required=True, location="json")
+
+###USER####
+user_parser = reqparse.RequestParser()
+user_parser.add_argument("email", type=inputs.email(), location="json")
+user_parser.add_argument("name", location="json")
+user_parser.add_argument("city", location="json")
+user_parser.add_argument("role", location="json")
+
+###SEARCH_USER####
+user_search_parser = search_parser.copy()
+user_search_parser.add_argument("name", default=None, location="args")
+user_search_parser.add_argument("email", default=None, location="args")
+user_search_parser.add_argument("city", default=None, location="args")
+user_search_parser.add_argument("role", default=None, location="args")
+
+
+###COMPETIION####
+competition_parser = reqparse.RequestParser()
+competition_parser.add_argument("name", location="json")
+competition_parser.add_argument("year", type=int, location="json")
+competition_parser.add_argument("city_id", type=int, location="json")
+competition_parser.add_argument("style_id", type=int, location="json")
+
+
+###SEARCH_COMPETITOIN####
+competition_search_parser = search_parser.copy()
+competition_search_parser.add_argument("name", default=None, location="args")
+competition_search_parser.add_argument("year", default=None, location="args")
+competition_search_parser.add_argument("city_id", default=None, location="args")
+competition_search_parser.add_argument("style_id", default=None, location="args")
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb2681ba5b39d4fb2f34f89f0c2910fb8fcc8088
--- /dev/null
+++ b/server/app/core/schemas.py
@@ -0,0 +1,23 @@
+from flask_restx import Namespace, Resource, abort, fields, inputs, model, reqparse
+
+user_schema = (
+    "User",
+    {
+        "id": fields.Integer(),
+        "name": fields.String(),
+        "email": fields.String(),
+        "role": fields.String(attribute=lambda x: x.role.name),
+        "city": fields.String(attribute=lambda x: x.city.name),
+    },
+)
+
+competition_schema = (
+    "Competition",
+    {
+        "id": fields.Integer(),
+        "name": fields.String(),
+        "year": fields.Integer(),
+        "style_id": fields.Integer(),
+        "city_id": fields.Integer(),
+    },
+)
diff --git a/server/app/core/utils/__init__.py b/server/app/core/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/server/app/utils/http_codes.py b/server/app/core/utils/http_codes.py
similarity index 100%
rename from server/app/utils/http_codes.py
rename to server/app/core/utils/http_codes.py
diff --git a/server/app/utils/test_helpers.py b/server/app/core/utils/test_helpers.py
similarity index 54%
rename from server/app/utils/test_helpers.py
rename to server/app/core/utils/test_helpers.py
index 821e4981daca8d935105ce1a05165ee2e11a37f3..968281d5af22c91a117aa4ebf58b0fe21ace11eb 100644
--- a/server/app/utils/test_helpers.py
+++ b/server/app/core/utils/test_helpers.py
@@ -1,9 +1,47 @@
 import json
 
-from app import db
+import app.core.controller as dbc
+import pytest
+from app.core import db
+from app.core.models import City, MediaType, QuestionType, Role, Style
+
+
+def add_default_values():
+    media_types = ["Image", "Video"]
+    question_types = ["Boolean", "Multiple", "Text"]
+    roles = ["Admin", "Editor"]
+    cities = ["Linköping"]
+
+    # Add media types
+    for item in media_types:
+        db.session.add(MediaType(item))
+
+    # Add question types
+    for item in question_types:
+        db.session.add(QuestionType(item))
+
+    # Add roles
+    for item in roles:
+        db.session.add(Role(item))
+
+    # Add cities
+    for item in cities:
+        db.session.add(City(item))
+
+    # Add deafult style
+    db.session.add(Style("Main Style", ""))
+
+    # Commit changes to db
+    db.session.commit()
+
+    # Add user with role and city
+    dbc.add.user("test@test.se", "password", "Admin", "Linköping")
 
 
 def post(client, url, data, headers=None):
+    if headers is None:
+        headers = {}
+    headers["Content-Type"] = "application/json"
     response = client.post(url, data=json.dumps(data), headers=headers)
     body = json.loads(response.data.decode())
     return response, body
@@ -16,6 +54,10 @@ def get(client, url, query_string=None, headers=None):
 
 
 def put(client, url, data, headers=None):
+    if headers is None:
+        headers = {}
+    headers["Content-Type"] = "application/json"
+
     response = client.put(url, data=json.dumps(data), headers=headers)
     body = json.loads(response.data.decode())
     return response, body
diff --git a/server/app/database/controller/__init__.py b/server/app/database/controller/__init__.py
deleted file mode 100644
index b3cfb1fc8c5cc4edcd7a01d0d829689084a3e063..0000000000000000000000000000000000000000
--- a/server/app/database/controller/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# import add, get
-from app.database.controller import add, get
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
deleted file mode 100644
index eb65ce784021ea768d7fafeb5d1a70b22fc94023..0000000000000000000000000000000000000000
--- a/server/app/database/controller/get.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from app import db
-from app.database.models import City, Competition, Role, Slide, Style, User
-from sqlalchemy import and_, or_
diff --git a/server/app/database/populate.py b/server/app/database/populate.py
deleted file mode 100644
index 7eb587107096619050b8c2c3dece8ce6996eddd9..0000000000000000000000000000000000000000
--- a/server/app/database/populate.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import app.database.controller as dbc
-from app import db
-from app.database.models import City, MediaType, QuestionType, Role
-
-media_types = ["Image", "Video"]
-question_types = ["Boolean", "Multiple", "Text"]
-roles = ["Admin", "Editor"]
-cities = ["Linköping"]
-
-
-def add_default_values():
-
-    # Add media types
-    for item in media_types:
-        db.session.add(MediaType(item))
-
-    # Add question types
-    for item in question_types:
-        db.session.add(QuestionType(item))
-
-    # Add roles
-    for item in roles:
-        db.session.add(Role(item))
-
-    # Add cities
-    for item in cities:
-        db.session.add(City(item))
-
-    # Commit changes to db
-    db.session.commit()
-
-    # Add user with role and city
-    dbc.add.user("test@test.se", "password", "Admin", "Linköping")
diff --git a/server/app/utils/helpers.py b/server/app/utils/helpers.py
deleted file mode 100644
index ddb0bfe0c54c4c7b71eda91072358e4509b9fa30..0000000000000000000000000000000000000000
--- a/server/app/utils/helpers.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import datetime
-
-
-def parse_date(date_to_parse):
-    return datetime.datetime.strptime(date_to_parse, "%Y-%m-%d").date()
diff --git a/server/app/utils/validator.py b/server/app/utils/validator.py
deleted file mode 100644
index e196f7b83685cef33c850dc17c4f9971d740a66b..0000000000000000000000000000000000000000
--- a/server/app/utils/validator.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from cerberus import Validator
-
-
-def validate_object(schema, obj, allow_unknown=False):
-    v = Validator(schema, allow_unknown)
-    if not v.validate(obj):
-        return v.errors
-
-
-_email_regex = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
-
-login_schema = {
-    "email": {"type": "string", "required": True, "regex": _email_regex},
-    "password": {"type": "string", "required": True, "minlength": 6, "maxlength": 128},
-}
-
-register_schema = {
-    "email": {"type": "string", "required": True, "regex": _email_regex},
-    "password": {"type": "string", "required": True, "minlength": 6, "maxlength": 128},
-    "role": {"type": "string", "required": True},
-    "city": {"type": "string", "required": True},
-}
-
-edit_user_schema = {
-    "name": {"type": "string", "required": False, "minlength": 1, "maxlength": 50},
-    "role": {"type": "string", "required": False},
-    "city": {"type": "string", "required": False},
-}
diff --git a/server/configmodule.py b/server/configmodule.py
index fb1117ad59aa01f7443fe4057564d8710b5c8077..f7c2935fa6e2f11747f14b7dfcfc5e426b6de1b5 100644
--- a/server/configmodule.py
+++ b/server/configmodule.py
@@ -1,3 +1,6 @@
+from datetime import timedelta
+
+
 class Config:
     DEBUG = False
     TESTING = False
@@ -5,6 +8,9 @@ class Config:
     JWT_SECRET_KEY = "super-secret"
     JWT_BLACKLIST_ENABLED = True
     JWT_BLACKLIST_TOKEN_CHECKS = ["access", "refresh"]
+    BUNDLE_ERRORS = True
+    JWT_ACCESS_TOKEN_EXPIRES = timedelta(days=2)
+    JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30)
 
 
 class DevelopmentConfig(Config):
diff --git a/server/populate.py b/server/populate.py
index 21c00dcaf5726d648bf82c40b901d7893e09d2a8..452769918189a1b6c4937578a0714ad777b281b6 100644
--- a/server/populate.py
+++ b/server/populate.py
@@ -1,6 +1,6 @@
-import app.database.controller as dbc
+import app.core.controller as dbc
 from app import create_app, db
-from app.database.models import City, MediaType, QuestionType, Role
+from app.core.models import City, MediaType, QuestionType, Role, Style
 
 user = {"email": "test@test.se", "password": "password", "role": "Admin", "city": "Linköping"}
 media_types = ["Image", "Video"]
@@ -31,6 +31,12 @@ def _add_items():
         db.session.add(City(item))
     db.session.commit()
 
+    # Add deafult style
+    db.session.add(Style("Main Style", ""))
+
+    # Commit changes to db
+    db.session.commit()
+
     # Add user with role and city
     dbc.add.user("test@test.se", "password", "Admin", "Linköping")
 
diff --git a/server/requirements.txt b/server/requirements.txt
index 31bb42fb147e77cea0e83c9af0230046efacb0b4..463ef4fe07f14b03b7771a25842170c8903f73f7 100644
Binary files a/server/requirements.txt and b/server/requirements.txt differ
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 1275b0f2e74c38c1942dc21bb45a2ed8627fa02f..6d6e16dcbd5a5c8128e78a832e94cb6321120497 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -1,50 +1,64 @@
 import json
 
-from app.database.populate import add_default_values
-from app.utils.test_helpers import delete, get, post, put
+from app.core.utils.test_helpers import add_default_values, get, post, put
 
-from tests import app, client
+from tests import app, client, db
+
+
+def test_competition(client):
+    add_default_values()
+
+    # Login in with default user
+    response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
+    assert response.status_code == 200
+    headers = {"Authorization": "Bearer " + body["access_token"]}
+
+    # Create competition
+    data = {"name": "c1", "year": 2020, "city_id": 1, "style_id": 1}
+    response, body = post(client, "/api/competitions", data, headers=headers)
+    assert response.status_code == 200
+    assert body["name"] == "c1"
+
+    # Get competition
+    response, body = get(client, "/api/competitions/1", headers=headers)
+    assert response.status_code == 200
+    assert body["name"] == "c1"
 
 
 def test_app(client):
     add_default_values()
 
     # Login in with default user
-    response, body = post(client, "/api/users/login", {"email": "test@test.se", "password": "password"})
-    item = body["result"][0]
-    headers = {"Authorization": "Bearer " + item["access_token"]}
+    response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
+    assert response.status_code == 200
+    headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Create user
     register_data = {"email": "test1@test.se", "password": "abc123", "role": "Admin", "city": "Linköping"}
-    response, body = post(client, "/api/users/", register_data, headers)
-    item = body["result"][0]
+    response, body = post(client, "/api/auth/signup", register_data, headers)
 
     assert response.status_code == 200
-    assert item["id"] == 2
-    assert "password" not in item
-    assert item["email"] == "test1@test.se"
+    assert body["id"] == 2
+    assert "password" not in body
 
     # Try loggin with wrong PASSWORD
-    response, body = post(client, "/api/users/login", {"email": "test1@test.se", "password": "abc1234"})
+    response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc1234"})
     assert response.status_code == 401
 
     # Try loggin with wrong Email
-    response, body = post(client, "/api/users/login", {"email": "testx@test.se", "password": "abc1234"})
+    response, body = post(client, "/api/auth/login", {"email": "testx@test.se", "password": "abc1234"})
     assert response.status_code == 401
 
     # Try loggin with right PASSWORD
-    response, body = post(client, "/api/users/login", {"email": "test1@test.se", "password": "abc123"})
-    item = body["result"][0]
-    headers = {"Authorization": "Bearer " + item["access_token"]}
+    response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc123"})
     assert response.status_code == 200
+    headers = {"Authorization": "Bearer " + body["access_token"]}
 
     # Get the current user
-    response, body = get(client, "/api/users/", headers=headers)
-    item = body["result"][0]
+    response, body = get(client, "/api/users", headers=headers)
     assert response.status_code == 200
-    assert item["email"] == "test1@test.se"
+    assert body["email"] == "test1@test.se"
 
-    response, body = put(client, "/api/users/", {"name": "carl carlsson"}, headers=headers)
-    item = body["result"][0]
+    response, body = put(client, "/api/users", {"name": "carl carlsson"}, headers=headers)
     assert response.status_code == 200
-    assert item["name"] == "Carl Carlsson"
+    assert body["name"] == "Carl Carlsson"
diff --git a/server/tests/test_db.py b/server/tests/test_db.py
index 832329cc47fbb5bef8597a3e747facf5d3423a83..f670e4926390e69bc101dcda39ab1769e6c64aa9 100644
--- a/server/tests/test_db.py
+++ b/server/tests/test_db.py
@@ -1,21 +1,5 @@
-import app.database.controller as dbc
-import pytest
-from app.database.models import (
-    City,
-    Competition,
-    Media,
-    MediaType,
-    Question,
-    QuestionAnswer,
-    QuestionType,
-    Role,
-    Slide,
-    Style,
-    Team,
-    User,
-)
-from app.database.populate import add_default_values
-from app.utils.test_helpers import assert_exists, assert_insert_fail, assert_object_values
+from app.core.models import City, Competition, Media, MediaType, Question, QuestionType, Role, Slide, Style, Team, User
+from app.core.utils.test_helpers import add_default_values, assert_exists, assert_insert_fail
 
 from tests import app, client, db