diff --git a/.gitignore b/.gitignore
index a125cfa..ddb5851 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,28 @@
-target
-
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
-*.iml
-
-## Directory-based project format:
-.idea/
-
-# jrebel configuration file
-rebel.xml
-
-# H2 db files
-db/**/*.db
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+generated/
+/db/
+.envrc
 
-# Elastic Beanstalk Files
-.elasticbeanstalk/*
-!.elasticbeanstalk/*.cfg.yml
-!.elasticbeanstalk/*.global.yml
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
 
-.deploy
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
 
-.envrc
-generated/
+### NetBeans ###
+nbproject/private/
 build/
-.gradle/
\ No newline at end of file
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
diff --git a/.travis.yml b/.travis.yml
index af9137e..58d73fc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,8 @@ install:
   - pip install awscli
   - aws --version
 
-script: ./gradlew clean build jacocoTestReport coveralls
+script:
+  - ./gradlew clean jooqGenerate build
 
 before_deploy:
   # Parse branch name and determine an environment to deploy
diff --git a/README.md b/README.md
index 1e61790..a7175b8 100644
--- a/README.md
+++ b/README.md
@@ -7,16 +7,18 @@ This repository is an example application for Spring Boot and Angular2 tutorial.
 
 [Demo](https://micropost.hana053.com/)
 
+* [Spring Boot](https://projects.spring.io/spring-boot/)
+* [Kotlin](https://kotlinlang.org/)
+* [jOOQ](https://www.jooq.org/)
+* [Flyway](https://flywaydb.org/)
 * JWT
-* [Querydsl](http://www.querydsl.com/)
-* [Spock](http://spockframework.org/)
 
 ## Getting Started
 
 Run Spring Boot.
 
 ```
-./gradlew bootRun
+./gradlew jooqGenerate bootRun
 ```
 
 Serve frontend app.
@@ -29,6 +31,7 @@ git clone https://github.com/springboot-angular2-tutorial/angular2-app.git
 Testing.
 
 ```
+./gradlew jooqGenerate # If you have not generated jOOQ code yet.
 ./gradlew test
 ```
 
@@ -39,22 +42,25 @@ API documentation.
 open http://localhost:8080/swagger-ui.html
 ```
 
+After you migrated DB.
+```
+./gradlew jooqGenerate # It will generate jOOQ code for your new schema.
+```
+
 ## Frequently asked questions
 
-* Q) Build becomes an error on IntelliJ IDEA with error message "QUser, QRelationship and etc can't be found".
-* A) You must configure setting for Annotation Processors.
-  1. Go to Preferences -> Build, Execution, Deployment -> Annotation Processors
-  2. Check Enable annotation processing checkbox
-  3. In "Store generated sources relative to:" select Module content root.
-  4. Finally, Build -> Build Project
-  
+* Q) IntelliJ IDEA is very slow when I use jOOQ with Kotlin.
+  * A) Refer [this ticket](https://youtrack.jetbrains.com/issue/KT-10978). In my case, [tuning memory config](https://youtrack.jetbrains.com/issue/KT-10978#comment=27-1519260) of IntelliJ IDEA worked.
+
+* Q) How can I run or debug app from IntelliJ IDEA?
+  * A) Use IntelliJ IDEA 2017.1 and run or debug Application.kt.
 
 ## Docker Support
 
 Dev
 
 ```bash
-./gradlew clean build -x test
+./gradlew clean jooqGenerate build -x test
 docker build -t YOUR_IMAGE_NAME .
 docker run -p 8080:8080 YOUR_IMAGE_NAME
 ```
@@ -62,7 +68,7 @@ docker run -p 8080:8080 YOUR_IMAGE_NAME
 Prod
 
 ```bash
-./gradlew clean build -x test
+./gradlew clean jooqGenerate build -x test
 docker build --build-arg JASYPT_ENCRYPTOR_PASSWORD=secret -t YOUR_IMAGE_NAME .
 docker run -p 8080:8080 \
   -e "SPRING_PROFILES_ACTIVE=prod" \
diff --git a/build.gradle b/build.gradle
index e7fba86..5dcbd1e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,79 +1,99 @@
+import org.jooq.util.GenerationTool
+import org.jooq.util.jaxb.*
+
 buildscript {
     ext {
-        springBootVersion = '1.5.1.RELEASE'
-        querydslVersion = '4.1.4'
+        kotlinVersion = '1.1.1'
+        springBootVersion = '1.5.2.RELEASE'
+        jooqVersion = '3.9.1'
+        flywayVersion = '4.1.2'
+        h2Version = '1.4.194'
         swaggerVersion = '2.6.1'
-        spockVersion = '1.1-groovy-2.4-rc-3'
     }
     repositories {
         jcenter()
         maven { url "https://plugins.gradle.org/m2/" }
     }
     dependencies {
-        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
-        classpath "org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1"
+        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
+        classpath "org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}"
+        classpath "org.jooq:jooq-codegen:${jooqVersion}"
+        classpath "com.h2database:h2:${h2Version}"
+        classpath "gradle.plugin.com.boxfuse.client:flyway-release:${flywayVersion}"
     }
 }
 
-plugins {
-    id 'jacoco'
-    id 'com.github.kt3k.coveralls' version '2.8.1'
-}
-
 apply plugin: 'java'
-apply plugin: 'groovy'
-apply plugin: 'idea'
+apply plugin: 'kotlin'
+apply plugin: 'kotlin-spring'
 apply plugin: 'org.springframework.boot'
-apply plugin: "com.github.kt3k.coveralls"
-
-sourceCompatibility = 1.8
+// It's just needed for JOOQ codegen
+apply plugin: "org.flywaydb.flyway"
 
 jar {
     baseName = 'springboot-angular2-tutorial'
     version = '0.0.1'
 }
 
-idea {
-    module {
-        sourceDirs += file('generated/')
-        generatedSourceDirs += file('generated/')
-    }
-}
+sourceCompatibility = 1.8
 
 repositories {
     jcenter()
 }
+
 dependencies {
-    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion
-    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: springBootVersion
-    compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion
-    compile group: 'org.springframework.boot', name: 'spring-boot-actuator', version: springBootVersion
-    compile group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: springBootVersion
-    compile group: 'org.springframework.boot', name: 'spring-boot-devtools', version: springBootVersion
+    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
+    compile group: 'org.springframework.boot', name: 'spring-boot-starter-security'
+    compile group: 'org.springframework.boot', name: 'spring-boot-starter-jooq'
+    compile group: 'org.springframework.boot', name: 'spring-boot-actuator'
+    compile group: 'org.springframework.boot', name: 'spring-boot-devtools'
+    compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlinVersion
+    compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlinVersion
+    compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.8.7'
+    compile group: 'org.jooq', name: 'jooq', version: jooqVersion
+    compile group: 'org.flywaydb', name: 'flyway-core', version: flywayVersion
+    compile group: 'com.zaxxer', name: 'HikariCP'
     compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.7.0'
-    compile group: 'org.bgee.log4jdbc-log4j2', name: 'log4jdbc-log4j2-jdbc4.1', version: '1.16'
-    compile group: 'org.flywaydb', name: 'flyway-core', version: '3.2.1'
-    compile group: 'com.querydsl', name: 'querydsl-jpa', version: querydslVersion
-    compile group: 'com.querydsl', name: 'querydsl-sql-spring', version: querydslVersion
-    compile "com.querydsl:querydsl-apt:$querydslVersion:jpa"
-    compile group: 'com.github.ulisesbocchio', name: 'jasypt-spring-boot-starter', version: '1.11'
-    compile group: 'com.zaxxer', name: 'HikariCP', version: '2.5.1'
     compile group: 'io.springfox', name: 'springfox-swagger2', version: swaggerVersion
     compile group: 'io.springfox', name: 'springfox-swagger-ui', version: swaggerVersion
-    compileOnly "org.projectlombok:lombok:1.16.14"
+    compile group: 'com.github.ulisesbocchio', name: 'jasypt-spring-boot-starter', version: '1.12'
     runtime group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '1.5.8'
-    runtime group: 'com.h2database', name: 'h2', version: '1.4.193'
-    testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion
-    testCompile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.8'
-    testCompile group: 'org.spockframework', name: 'spock-core', version: spockVersion
-    testCompile group: 'org.spockframework', name: 'spock-spring', version: spockVersion
-    testCompile group: 'com.jayway.jsonpath', name: 'json-path', version: '2.2.0'
+    runtime group: 'com.h2database', name: 'h2', version: h2Version
+    testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
+    testCompile group: 'org.jetbrains.kotlin', name: 'kotlin-test-junit', version: kotlinVersion
+    testCompile group: 'org.assertj', name: 'assertj-core', version: '3.6.2'
+    testCompile group: 'com.nhaarman', name: 'mockito-kotlin', version: '1.1.0'
+    testCompile group: 'com.beust', name: 'klaxon', version: '0.31'
+}
+
+flyway {
+    url = 'jdbc:h2:./db/tmp;MODE=MySQL'
+    user = 'sa'
+    password = ''
 }
 
-jacocoTestReport {
-    reports {
-        xml.enabled = true // coveralls plugin depends on xml format report
-        html.enabled = true
+task jooqGenerate {
+    doLast {
+        Configuration configuration = new Configuration()
+                .withJdbc(new Jdbc()
+                    .withDriver("org.h2.Driver")
+                    .withUrl("jdbc:h2:./db/tmp;MODE=MySQL")
+                    .withUser("sa")
+                    .withPassword("")
+                )
+                .withGenerator(new Generator()
+                    .withDatabase(new Database()
+                        .withInputSchema("public")
+                        .withOutputSchemaToDefault(true)
+                    )
+                    .withTarget(new Target()
+                        .withPackageName("com.myapp.generated")
+                        .withDirectory("src/main/java")
+                    )
+                )
+        GenerationTool.generate(configuration)
     }
 }
+jooqGenerate.dependsOn flywayMigrate
 
diff --git a/db/.gitkeep b/db/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..587b727
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+kotlin.incremental=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index af6d662..ca78035 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 5db2ff7..4f2492d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-#Wed Feb 22 11:26:30 ICT 2017
+#Thu Feb 23 11:57:23 ICT 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index 71ddebd..5394d17 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -11,7 +11,7 @@ readonly AWS_ACCOUNT_NUMBER=$(aws sts get-caller-identity --output text --query
 readonly IMAGE_URL=${AWS_ACCOUNT_NUMBER}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${DOCKER_NAME}
 
 # Build
-./gradlew build -x test
+./gradlew jooqGenerate build -x test
 
 # Ensure docker repository exists
 aws ecr describe-repositories --repository-names ${DOCKER_NAME} > /dev/null 2>&1 || \
diff --git a/src/main/java/com/myapp/Application.java b/src/main/java/com/myapp/Application.java
deleted file mode 100644
index b5aae2b..0000000
--- a/src/main/java/com/myapp/Application.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.myapp;
-
-import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-@EnableEncryptableProperties
-public class Application {
-
-    @SuppressWarnings("unused")
-    private static final Logger logger = LoggerFactory.getLogger(Application.class);
-
-    public static void main(String[] args) {
-        SpringApplication.run(Application.class, args);
-    }
-}
-
diff --git a/src/main/java/com/myapp/Utils.java b/src/main/java/com/myapp/Utils.java
deleted file mode 100644
index 0b9be56..0000000
--- a/src/main/java/com/myapp/Utils.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.myapp;
-
-import javax.xml.bind.DatatypeConverter;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-public class Utils {
-
-    public static String md5(String source) {
-        byte[] bytes;
-        try {
-            bytes = MessageDigest.getInstance("MD5")
-                    .digest(source.getBytes("UTF-8"));
-        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
-            return null;
-        }
-        return DatatypeConverter.printHexBinary(bytes).toLowerCase();
-    }
-
-}
diff --git a/src/main/java/com/myapp/auth/SecurityConfig.java b/src/main/java/com/myapp/auth/SecurityConfig.java
deleted file mode 100644
index 8f51f0e..0000000
--- a/src/main/java/com/myapp/auth/SecurityConfig.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.myapp.auth;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.annotation.Order;
-import org.springframework.http.HttpMethod;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
-@Configuration
-@EnableWebSecurity
-@Order(1)
-public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
-    private final UserDetailsService userService;
-    private final StatelessAuthenticationFilter statelessAuthenticationFilter;
-
-    @Autowired
-    public SecurityConfig(UserDetailsService userService, StatelessAuthenticationFilter statelessAuthenticationFilter) {
-        super(true);
-        this.userService = userService;
-        this.statelessAuthenticationFilter = statelessAuthenticationFilter;
-    }
-
-    @Override
-    protected void configure(HttpSecurity http) throws Exception {
-        // we use jwt so that we can disable csrf protection
-        http.csrf().disable();
-
-        http
-                .exceptionHandling().and()
-                .anonymous().and()
-                .servletApi().and()
-                .headers().cacheControl()
-        ;
-
-        http.authorizeRequests()
-                .antMatchers(HttpMethod.GET, "/api/users").hasRole("USER")
-                .antMatchers(HttpMethod.GET, "/api/users/me").hasRole("USER")
-                .antMatchers(HttpMethod.PATCH, "/api/users/me").hasRole("USER")
-                .antMatchers(HttpMethod.GET, "/api/users/me/microposts").hasRole("USER")
-                .antMatchers(HttpMethod.POST, "/api/microposts/**").hasRole("USER")
-                .antMatchers(HttpMethod.DELETE, "/api/microposts/**").hasRole("USER")
-                .antMatchers(HttpMethod.POST, "/api/relationships/**").hasRole("USER")
-                .antMatchers(HttpMethod.DELETE, "/api/relationships/**").hasRole("USER")
-                .antMatchers(HttpMethod.GET, "/api/feed").hasRole("USER")
-                .and()
-                .exceptionHandling()
-                .authenticationEntryPoint(new Http401AuthenticationEntryPoint("'Bearer token_type=\"JWT\"'"));
-
-        http.addFilterBefore(statelessAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
-    }
-
-    @Bean
-    @Override
-    public AuthenticationManager authenticationManagerBean() throws Exception {
-        return super.authenticationManagerBean();
-    }
-
-    /**
-     * Prevent StatelessAuthenticationFilter will be added to Spring Boot filter chain.
-     * Only Spring Security must use it.
-     */
-    @Bean
-    public FilterRegistrationBean registration(StatelessAuthenticationFilter filter) {
-        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
-        registration.setEnabled(false);
-        return registration;
-    }
-
-    @Override
-    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
-        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
-    }
-
-    @Override
-    protected UserDetailsService userDetailsService() {
-        return userService;
-    }
-
-}
-
-
diff --git a/src/main/java/com/myapp/auth/StatelessAuthenticationFilter.java b/src/main/java/com/myapp/auth/StatelessAuthenticationFilter.java
deleted file mode 100644
index e2b5823..0000000
--- a/src/main/java/com/myapp/auth/StatelessAuthenticationFilter.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.myapp.auth;
-
-import io.jsonwebtoken.JwtException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Component;
-import org.springframework.web.filter.GenericFilterBean;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-@Component
-class StatelessAuthenticationFilter extends GenericFilterBean {
-
-    private final TokenAuthenticationService tokenAuthenticationService;
-
-    StatelessAuthenticationFilter(TokenAuthenticationService tokenAuthenticationService) {
-        this.tokenAuthenticationService = tokenAuthenticationService;
-    }
-
-    @Override
-    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
-        try {
-            Authentication authentication = tokenAuthenticationService.getAuthentication((HttpServletRequest) req);
-            SecurityContextHolder.getContext().setAuthentication(authentication);
-            chain.doFilter(req, res);
-            SecurityContextHolder.getContext().setAuthentication(null);
-        } catch (AuthenticationException | JwtException e) {
-            SecurityContextHolder.clearContext();
-            ((HttpServletResponse) res).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-        }
-    }
-}
diff --git a/src/main/java/com/myapp/auth/TokenAuthenticationService.java b/src/main/java/com/myapp/auth/TokenAuthenticationService.java
deleted file mode 100644
index ec2efce..0000000
--- a/src/main/java/com/myapp/auth/TokenAuthenticationService.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.myapp.auth;
-
-import org.springframework.security.core.Authentication;
-
-import javax.servlet.http.HttpServletRequest;
-
-public interface TokenAuthenticationService {
-
-    Authentication getAuthentication(HttpServletRequest request);
-}
-
diff --git a/src/main/java/com/myapp/auth/TokenAuthenticationServiceImpl.java b/src/main/java/com/myapp/auth/TokenAuthenticationServiceImpl.java
deleted file mode 100644
index d3842c5..0000000
--- a/src/main/java/com/myapp/auth/TokenAuthenticationServiceImpl.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.myapp.auth;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-import org.springframework.stereotype.Service;
-
-import javax.servlet.http.HttpServletRequest;
-
-@Service
-class TokenAuthenticationServiceImpl implements TokenAuthenticationService {
-
-    private final TokenHandler tokenHandler;
-
-    @Autowired
-    TokenAuthenticationServiceImpl(TokenHandler tokenHandler) {
-        this.tokenHandler = tokenHandler;
-    }
-
-    public Authentication getAuthentication(HttpServletRequest request) {
-        final String authHeader = request.getHeader("authorization");
-        if (authHeader == null) return null;
-        if (!authHeader.startsWith("Bearer")) return null;
-
-        final String jwt = authHeader.substring(7);
-        if (jwt.isEmpty()) return null;
-
-        return tokenHandler
-                .parseUserFromToken(jwt)
-                .map(UserAuthentication::new)
-                .orElse(null);
-    }
-}
-
diff --git a/src/main/java/com/myapp/auth/TokenHandler.java b/src/main/java/com/myapp/auth/TokenHandler.java
deleted file mode 100644
index 5c9a123..0000000
--- a/src/main/java/com/myapp/auth/TokenHandler.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.myapp.auth;
-
-import com.myapp.domain.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Component;
-
-import java.util.Optional;
-
-@Component
-public interface TokenHandler {
-
-    Optional<UserDetails> parseUserFromToken(String token);
-
-    String createTokenForUser(User user);
-
-}
diff --git a/src/main/java/com/myapp/auth/TokenHandlerImpl.java b/src/main/java/com/myapp/auth/TokenHandlerImpl.java
deleted file mode 100644
index de39fad..0000000
--- a/src/main/java/com/myapp/auth/TokenHandlerImpl.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.myapp.auth;
-
-import com.myapp.domain.User;
-import com.myapp.repository.UserRepository;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureAlgorithm;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Component;
-
-import java.time.ZonedDateTime;
-import java.util.Date;
-import java.util.Optional;
-
-@Component
-public final class TokenHandlerImpl implements TokenHandler {
-
-    @SuppressWarnings("unused")
-    private static final Logger logger = LoggerFactory.getLogger(TokenHandlerImpl.class);
-
-    private final String secret;
-
-    private final UserRepository userRepository;
-
-    @Autowired
-    public TokenHandlerImpl(@Value("${app.jwt.secret}") String secret,
-                            UserRepository userRepository) {
-        this.secret = secret;
-        this.userRepository = userRepository;
-    }
-
-    @Override
-    public Optional<UserDetails> parseUserFromToken(String token) {
-        final String subject = Jwts.parser()
-                .setSigningKey(secret)
-                .parseClaimsJws(token)
-                .getBody()
-                .getSubject();
-        final Long userId = Long.valueOf(subject);
-        final User user = userRepository.findOne(userId);
-
-        return Optional.ofNullable(user);
-    }
-
-    @Override
-    public String createTokenForUser(User user) {
-        final ZonedDateTime afterOneWeek = ZonedDateTime.now().plusWeeks(1);
-
-        return Jwts.builder()
-                .setSubject(user.getId().toString())
-                .signWith(SignatureAlgorithm.HS512, secret)
-                .setExpiration(Date.from(afterOneWeek.toInstant()))
-                .compact();
-    }
-}
-
diff --git a/src/main/java/com/myapp/auth/UserAuthentication.java b/src/main/java/com/myapp/auth/UserAuthentication.java
deleted file mode 100644
index 1499e16..0000000
--- a/src/main/java/com/myapp/auth/UserAuthentication.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.myapp.auth;
-
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-import java.util.Collection;
-
-public class UserAuthentication implements Authentication {
-
-    private final UserDetails user;
-    private boolean authenticated = true;
-
-    public UserAuthentication(UserDetails user) {
-        this.user = user;
-    }
-
-    @Override
-    public Collection<? extends GrantedAuthority> getAuthorities() {
-        return user.getAuthorities();
-    }
-
-    @Override
-    public Object getCredentials() {
-        return user.getPassword();
-    }
-
-    @Override
-    public UserDetails getDetails() {
-        return user;
-    }
-
-    @Override
-    public Object getPrincipal() {
-        return user.getUsername();
-    }
-
-    @Override
-    public boolean isAuthenticated() {
-        return authenticated;
-    }
-
-    @Override
-    public void setAuthenticated(boolean authenticated) throws IllegalArgumentException {
-        this.authenticated = authenticated;
-    }
-
-    @Override
-    public String getName() {
-        return user.getUsername();
-    }
-}
diff --git a/src/main/java/com/myapp/config/PooledDatasourceConfig.java b/src/main/java/com/myapp/config/PooledDatasourceConfig.java
deleted file mode 100644
index c13d6d2..0000000
--- a/src/main/java/com/myapp/config/PooledDatasourceConfig.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.myapp.config;
-
-import com.zaxxer.hikari.HikariConfig;
-import com.zaxxer.hikari.HikariDataSource;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-
-import javax.sql.DataSource;
-import java.sql.SQLException;
-
-@Configuration
-@Profile({"dev2", "stg", "prod"})
-@ConfigurationProperties(prefix = "spring.datasource")
-public class PooledDatasourceConfig extends HikariConfig {
-
-    @Bean
-    public DataSource dataSource() throws SQLException {
-        return new HikariDataSource(this);
-    }
-}
diff --git a/src/main/java/com/myapp/config/QueryDSLConfig.java b/src/main/java/com/myapp/config/QueryDSLConfig.java
deleted file mode 100644
index 7c03394..0000000
--- a/src/main/java/com/myapp/config/QueryDSLConfig.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.myapp.config;
-
-import com.querydsl.jpa.impl.JPAQueryFactory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-
-@Configuration
-public class QueryDSLConfig {
-
-    @PersistenceContext
-    private EntityManager entityManager;
-
-    @Bean
-    public JPAQueryFactory jpaQueryFactory() {
-        return new JPAQueryFactory(entityManager);
-    }
-
-}
diff --git a/src/main/java/com/myapp/config/Swagger2Config.java b/src/main/java/com/myapp/config/Swagger2Config.java
deleted file mode 100644
index adc1b89..0000000
--- a/src/main/java/com/myapp/config/Swagger2Config.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.myapp.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-import static springfox.documentation.builders.PathSelectors.regex;
-
-@Configuration
-@EnableSwagger2
-public class Swagger2Config {
-
-    @Bean
-    public Docket swaggerSpringMvcPlugin() {
-        return new Docket(DocumentationType.SWAGGER_2)
-                .select()
-                .paths(regex("/api.*"))
-                .build();
-    }
-
-}
diff --git a/src/main/java/com/myapp/controller/AuthController.java b/src/main/java/com/myapp/controller/AuthController.java
deleted file mode 100644
index 9eab04c..0000000
--- a/src/main/java/com/myapp/controller/AuthController.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.myapp.controller;
-
-import com.myapp.auth.TokenHandler;
-import com.myapp.service.SecurityContextService;
-import lombok.Value;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * @author riccardo.causo
- */
-@RestController
-@RequestMapping("/api/auth")
-public class AuthController {
-
-    private final AuthenticationManager authenticationManager;
-    private final TokenHandler tokenHandler;
-    private final SecurityContextService securityContextService;
-
-    @Autowired
-    AuthController(AuthenticationManager authenticationManager,
-                   TokenHandler tokenHandler,
-                   SecurityContextService securityContextService) {
-        this.authenticationManager = authenticationManager;
-        this.tokenHandler = tokenHandler;
-        this.securityContextService = securityContextService;
-    }
-
-    @RequestMapping(method = RequestMethod.POST)
-    public AuthResponse auth(@RequestBody AuthParams params) throws AuthenticationException {
-        final UsernamePasswordAuthenticationToken loginToken = params.toAuthenticationToken();
-        final Authentication authentication = authenticationManager.authenticate(loginToken);
-        SecurityContextHolder.getContext().setAuthentication(authentication);
-
-        return securityContextService.currentUser().map(u -> {
-            final String token = tokenHandler.createTokenForUser(u);
-            return new AuthResponse(token);
-        }).orElseThrow(RuntimeException::new); // it does not happen.
-    }
-
-    @Value
-    private static final class AuthParams {
-        private final String email;
-        private final String password;
-
-        UsernamePasswordAuthenticationToken toAuthenticationToken() {
-            return new UsernamePasswordAuthenticationToken(email, password);
-        }
-    }
-
-    @Value
-    private static final class AuthResponse {
-        private final String token;
-    }
-
-}
diff --git a/src/main/java/com/myapp/controller/FeedController.java b/src/main/java/com/myapp/controller/FeedController.java
deleted file mode 100644
index 1a80f8a..0000000
--- a/src/main/java/com/myapp/controller/FeedController.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.myapp.controller;
-
-import com.myapp.dto.PageParams;
-import com.myapp.dto.PostDTO;
-import com.myapp.service.MicropostService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/feed")
-public class FeedController {
-
-    @SuppressWarnings("UnusedDeclaration")
-    private static final Logger logger = LoggerFactory.getLogger(FeedController.class);
-
-    private final MicropostService micropostService;
-
-    @Autowired
-    public FeedController(MicropostService micropostService) {
-        this.micropostService = micropostService;
-    }
-
-    @RequestMapping(method = RequestMethod.GET)
-    public List<PostDTO> feed(PageParams pageParams) {
-        return micropostService.findAsFeed(pageParams);
-    }
-
-}
diff --git a/src/main/java/com/myapp/controller/MicropostController.java b/src/main/java/com/myapp/controller/MicropostController.java
deleted file mode 100644
index 7ab8db8..0000000
--- a/src/main/java/com/myapp/controller/MicropostController.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.myapp.controller;
-
-import com.myapp.domain.Micropost;
-import com.myapp.dto.MicropostParams;
-import com.myapp.service.MicropostService;
-import com.myapp.service.exceptions.NotPermittedException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-
-@RestController
-@RequestMapping("/api/microposts")
-public class MicropostController {
-
-    private final MicropostService micropostService;
-
-    @Autowired
-    public MicropostController(MicropostService micropostService) {
-        this.micropostService = micropostService;
-    }
-
-    @RequestMapping(method = RequestMethod.POST)
-    public Micropost create(@RequestBody MicropostParams params) {
-        return micropostService.saveMyPost(params.toPost());
-    }
-
-    @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
-    public void delete(@PathVariable("id") Long id) throws NotPermittedException {
-        micropostService.delete(id);
-    }
-
-    @ResponseStatus(value = HttpStatus.FORBIDDEN)
-    @ExceptionHandler(NotPermittedException.class)
-    public void handleNoPermission() {
-    }
-
-}
diff --git a/src/main/java/com/myapp/controller/RelationshipController.java b/src/main/java/com/myapp/controller/RelationshipController.java
deleted file mode 100644
index 9955581..0000000
--- a/src/main/java/com/myapp/controller/RelationshipController.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.myapp.controller;
-
-import com.myapp.service.RelationshipService;
-import com.myapp.service.exceptions.RelationshipNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-
-@RestController
-@RequestMapping("/api/relationships")
-public class RelationshipController {
-
-    private final RelationshipService relationshipService;
-
-    @Autowired
-    public RelationshipController(RelationshipService relationshipService) {
-        this.relationshipService = relationshipService;
-    }
-
-    @RequestMapping(value = "/to/{followedId}", method = RequestMethod.POST)
-    public void follow(@PathVariable("followedId") Long followedId) {
-        relationshipService.follow(followedId);
-    }
-
-    @RequestMapping(value = "/to/{followedId}", method = RequestMethod.DELETE)
-    public void unfollow(@PathVariable("followedId") Long followedId) throws RelationshipNotFoundException {
-        relationshipService.unfollow(followedId);
-    }
-
-    @ResponseStatus(value = HttpStatus.NOT_FOUND)
-    @ExceptionHandler(RelationshipNotFoundException.class)
-    public void handleRelationshipNotFound() {
-    }
-
-}
diff --git a/src/main/java/com/myapp/controller/UserController.java b/src/main/java/com/myapp/controller/UserController.java
deleted file mode 100644
index 85a4339..0000000
--- a/src/main/java/com/myapp/controller/UserController.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.myapp.controller;
-
-import com.myapp.domain.User;
-import com.myapp.dto.ErrorResponse;
-import com.myapp.dto.UserDTO;
-import com.myapp.dto.UserParams;
-import com.myapp.service.UserService;
-import com.myapp.service.exceptions.UserNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.dao.DataIntegrityViolationException;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.access.AccessDeniedException;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Nullable;
-import javax.validation.Valid;
-import java.util.Optional;
-
-@RestController
-@RequestMapping("/api/users")
-public class UserController {
-
-    private static final Integer DEFAULT_PAGE_SIZE = 5;
-
-    private final UserService userService;
-
-    @Autowired
-    public UserController(UserService userService) {
-        this.userService = userService;
-    }
-
-    @RequestMapping(method = RequestMethod.GET)
-    public Page<UserDTO> list(@RequestParam(value = "page", required = false) @Nullable Integer page,
-                              @RequestParam(value = "size", required = false) @Nullable Integer size) {
-        final PageRequest pageable = new PageRequest(
-                Optional.ofNullable(page).orElse(1) - 1,
-                Optional.ofNullable(size).orElse(DEFAULT_PAGE_SIZE));
-        return userService.findAll(pageable);
-    }
-
-    @RequestMapping(method = RequestMethod.POST)
-    public User create(@Valid @RequestBody UserParams params) {
-        return userService.create(params);
-    }
-
-    @RequestMapping(method = RequestMethod.GET, path = "{id:\\d+}")
-    public UserDTO show(@PathVariable("id") Long id) throws UserNotFoundException {
-        return userService.findOne(id)
-                .orElseThrow(UserNotFoundException::new);
-    }
-
-    @RequestMapping(method = RequestMethod.GET, path = "/me")
-    public UserDTO showMe() {
-        return userService.findMe()
-                .orElseThrow(() -> new AccessDeniedException(""));
-    }
-
-    @RequestMapping(method = RequestMethod.PATCH, path = "/me")
-    public void updateMe(@Valid @RequestBody UserParams params) {
-        userService.updateMe(params);
-    }
-
-    @ResponseStatus(HttpStatus.BAD_REQUEST)
-    @ExceptionHandler(DataIntegrityViolationException.class)
-    public ErrorResponse handleValidationException(DataIntegrityViolationException e) {
-        return new ErrorResponse("email_already_taken", "This email is already taken.");
-    }
-
-    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No user")
-    @ExceptionHandler(UserNotFoundException.class)
-    public void handleUserNotFound() {
-    }
-
-}
diff --git a/src/main/java/com/myapp/controller/UserMicropostController.java b/src/main/java/com/myapp/controller/UserMicropostController.java
deleted file mode 100644
index e0075f0..0000000
--- a/src/main/java/com/myapp/controller/UserMicropostController.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.myapp.controller;
-
-import com.myapp.dto.PageParams;
-import com.myapp.dto.PostDTO;
-import com.myapp.service.MicropostService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/users")
-public class UserMicropostController {
-
-    private final MicropostService micropostService;
-
-    @Autowired
-    public UserMicropostController(MicropostService micropostService) {
-        this.micropostService = micropostService;
-    }
-
-    @RequestMapping(method = RequestMethod.GET, path = "/{userId:\\d+}/microposts")
-    public List<PostDTO> list(@PathVariable("userId") Long userId, PageParams pageParams) {
-        return micropostService.findByUser(userId, pageParams);
-    }
-
-    @RequestMapping(method = RequestMethod.GET, path = "/me/microposts")
-    public List<PostDTO> list(PageParams pageParams) {
-        return micropostService.findMyPosts(pageParams);
-    }
-
-}
diff --git a/src/main/java/com/myapp/controller/UserRelationshipController.java b/src/main/java/com/myapp/controller/UserRelationshipController.java
deleted file mode 100644
index 88aedc1..0000000
--- a/src/main/java/com/myapp/controller/UserRelationshipController.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.myapp.controller;
-
-import com.myapp.dto.PageParams;
-import com.myapp.dto.RelatedUserDTO;
-import com.myapp.service.RelationshipService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/users/{userId}")
-public class UserRelationshipController {
-
-    private final RelationshipService relationshipService;
-
-    @Autowired
-    public UserRelationshipController(RelationshipService relationshipService) {
-        this.relationshipService = relationshipService;
-    }
-
-    @RequestMapping(method = RequestMethod.GET, path = "/followings")
-    public List<RelatedUserDTO> followings(@PathVariable("userId") long userId, PageParams pageParams) {
-        return relationshipService.findFollowings(userId, pageParams);
-    }
-
-    @RequestMapping(method = RequestMethod.GET, path = "/followers")
-    public List<RelatedUserDTO> followers(@PathVariable("userId") long userId, PageParams pageParams) {
-        return relationshipService.findFollowers(userId, pageParams);
-    }
-
-}
diff --git a/src/main/java/com/myapp/domain/Micropost.java b/src/main/java/com/myapp/domain/Micropost.java
deleted file mode 100644
index e39930d..0000000
--- a/src/main/java/com/myapp/domain/Micropost.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.myapp.domain;
-
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.*;
-import javax.validation.constraints.NotNull;
-import java.util.Date;
-
-@Entity
-@Table(name = "micropost")
-@Data
-@NoArgsConstructor
-public class Micropost {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
-    private Long id;
-
-    @NotNull
-    @ManyToOne(fetch = FetchType.EAGER)
-    private User user;
-
-    @NotNull
-    private String content;
-
-    @NotNull
-    @Column(name = "created_at")
-    private Date createdAt;
-
-    public Micropost(User user, String content) {
-        this.user = user;
-        this.content = content;
-    }
-
-    public Micropost(String content) {
-        this.content = content;
-    }
-
-    @PrePersist
-    protected void onCreate() {
-        createdAt = new Date();
-    }
-
-}
diff --git a/src/main/java/com/myapp/domain/Relationship.java b/src/main/java/com/myapp/domain/Relationship.java
deleted file mode 100644
index 59e31e1..0000000
--- a/src/main/java/com/myapp/domain/Relationship.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.myapp.domain;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.*;
-import javax.validation.constraints.NotNull;
-import java.io.Serializable;
-
-@Entity
-@Table(name = "relationship", uniqueConstraints = @UniqueConstraint(columnNames = {"follower_id", "followed_id"}))
-@NoArgsConstructor
-@Data
-public class Relationship implements Serializable {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
-    private Long id;
-
-    @NotNull
-    @ManyToOne
-    @JsonIgnore
-    private User follower;
-
-    @NotNull
-    @ManyToOne
-    @JsonIgnore
-    private User followed;
-
-    public Relationship(User follower, User followed) {
-        this.follower = follower;
-        this.followed = followed;
-    }
-}
diff --git a/src/main/java/com/myapp/domain/User.java b/src/main/java/com/myapp/domain/User.java
deleted file mode 100644
index 0b4e65b..0000000
--- a/src/main/java/com/myapp/domain/User.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.myapp.domain;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.ToString;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-import javax.persistence.*;
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Pattern;
-import javax.validation.constraints.Size;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-@Entity
-@Table(name = "user", uniqueConstraints = @UniqueConstraint(columnNames = {"username"}))
-@ToString
-public class User implements UserDetails {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
-    @Getter
-    @Setter
-    private Long id;
-
-    @NotNull
-    @Size(min = 4, max = 30)
-    @Setter
-    @Pattern(regexp = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$")
-    private String username;
-
-    @NotNull
-    private String password;
-
-    @NotNull
-    @Size(min = 4, max = 30)
-    @Getter
-    @Setter
-    private String name;
-
-    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, orphanRemoval = true)
-    @JsonIgnore
-    private List<Micropost> microposts;
-
-    @OneToMany(mappedBy = "follower", fetch = FetchType.LAZY, orphanRemoval = true)
-    @JsonIgnore
-    private List<Relationship> followerRelations;
-
-    @OneToMany(mappedBy = "followed", fetch = FetchType.LAZY, orphanRemoval = true)
-    @JsonIgnore
-    private List<Relationship> followedRelations;
-
-    @Override
-    @JsonProperty("email")
-    public String getUsername() {
-        return username;
-    }
-
-    @Override
-    @JsonIgnore
-    public String getPassword() {
-        return password;
-    }
-
-    @JsonProperty
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    @Override
-    @JsonIgnore
-    public Collection<? extends GrantedAuthority> getAuthorities() {
-        return Collections.singleton(() -> "ROLE_USER");
-    }
-
-    @Override
-    @JsonIgnore
-    public boolean isAccountNonExpired() {
-        return true;
-    }
-
-    @Override
-    @JsonIgnore
-    public boolean isAccountNonLocked() {
-        return true;
-    }
-
-    @Override
-    @JsonIgnore
-    public boolean isCredentialsNonExpired() {
-        return true;
-    }
-
-    @Override
-    @JsonIgnore
-    public boolean isEnabled() {
-        return true;
-    }
-}
diff --git a/src/main/java/com/myapp/domain/UserStats.java b/src/main/java/com/myapp/domain/UserStats.java
deleted file mode 100644
index 8cc2ac4..0000000
--- a/src/main/java/com/myapp/domain/UserStats.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.myapp.domain;
-
-import com.querydsl.core.annotations.QueryProjection;
-import lombok.Builder;
-import lombok.Value;
-
-@Value
-@Builder
-public class UserStats {
-
-    private final long micropostCnt;
-    private final long followingCnt;
-    private final long followerCnt;
-
-    @QueryProjection
-    public UserStats(long micropostCnt, long followingCnt, long followerCnt) {
-        this.micropostCnt = micropostCnt;
-        this.followingCnt = followingCnt;
-        this.followerCnt = followerCnt;
-    }
-
-}
diff --git a/src/main/java/com/myapp/dto/ErrorResponse.java b/src/main/java/com/myapp/dto/ErrorResponse.java
deleted file mode 100644
index 6f00cbc..0000000
--- a/src/main/java/com/myapp/dto/ErrorResponse.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.myapp.dto;
-
-import lombok.Value;
-
-@Value
-public class ErrorResponse {
-    private final String code;
-    private final String message;
-}
diff --git a/src/main/java/com/myapp/dto/MicropostParams.java b/src/main/java/com/myapp/dto/MicropostParams.java
deleted file mode 100644
index 7119393..0000000
--- a/src/main/java/com/myapp/dto/MicropostParams.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.myapp.dto;
-
-import com.myapp.domain.Micropost;
-import lombok.Value;
-
-@Value
-public class MicropostParams {
-
-    private String content;
-
-    public Micropost toPost() {
-       return new Micropost(content);
-    }
-}
diff --git a/src/main/java/com/myapp/dto/PageParams.java b/src/main/java/com/myapp/dto/PageParams.java
deleted file mode 100644
index aa7ac7c..0000000
--- a/src/main/java/com/myapp/dto/PageParams.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.myapp.dto;
-
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.util.Optional;
-
-@Data
-@NoArgsConstructor
-public final class PageParams {
-
-    private static final int DEFAULT_COUNT = 20;
-
-    private Long sinceId;
-    private Long maxId;
-    private int count = DEFAULT_COUNT;
-
-    public Optional<Long> getSinceId() {
-        return Optional.ofNullable(sinceId);
-    }
-
-    public Optional<Long> getMaxId() {
-        return Optional.ofNullable(maxId);
-    }
-
-
-}
diff --git a/src/main/java/com/myapp/dto/PostDTO.java b/src/main/java/com/myapp/dto/PostDTO.java
deleted file mode 100644
index dad1fe2..0000000
--- a/src/main/java/com/myapp/dto/PostDTO.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.myapp.dto;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.myapp.Utils;
-import com.myapp.domain.Micropost;
-import com.myapp.domain.UserStats;
-import lombok.Builder;
-import lombok.NonNull;
-import lombok.Value;
-
-import java.util.Date;
-
-@Value
-@Builder
-public class PostDTO {
-
-    private final long id;
-    @NonNull
-    private final String content;
-    @NonNull
-    private final Date createdAt;
-    @JsonProperty("user")
-    private final UserDTO userDTO;
-    private final Boolean isMyPost;
-
-    public static PostDTO newInstance(Micropost post, UserStats userStats, Boolean isMyPost, Boolean isFollowedByMe) {
-        final UserDTO userDTO = UserDTO.builder()
-                .id(post.getUser().getId())
-                .name(post.getUser().getName())
-                .userStats(userStats)
-                .avatarHash(Utils.md5(post.getUser().getUsername()))
-                .isFollowedByMe(isFollowedByMe)
-                .build();
-
-        return PostDTO.builder()
-                .id(post.getId())
-                .content(post.getContent())
-                .createdAt(post.getCreatedAt())
-                .userDTO(userDTO)
-                .isMyPost(isMyPost)
-                .build();
-    }
-
-    public static PostDTO newInstance(Micropost post, Boolean isMyPost) {
-        return PostDTO.newInstance(post, null, isMyPost, null);
-    }
-
-}
diff --git a/src/main/java/com/myapp/dto/RelatedUserDTO.java b/src/main/java/com/myapp/dto/RelatedUserDTO.java
deleted file mode 100644
index de5bd67..0000000
--- a/src/main/java/com/myapp/dto/RelatedUserDTO.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.myapp.dto;
-
-import com.myapp.domain.UserStats;
-import lombok.Builder;
-import lombok.NonNull;
-import lombok.Value;
-
-@Value
-@Builder
-public class RelatedUserDTO {
-
-    private final long id;
-    @NonNull
-    private final String name;
-    @NonNull
-    private final String avatarHash;
-    @NonNull
-    private final UserStats userStats;
-    private final long relationshipId;
-    private final Boolean isFollowedByMe;
-
-}
diff --git a/src/main/java/com/myapp/dto/UserDTO.java b/src/main/java/com/myapp/dto/UserDTO.java
deleted file mode 100644
index 7ee2ca2..0000000
--- a/src/main/java/com/myapp/dto/UserDTO.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.myapp.dto;
-
-import com.myapp.domain.UserStats;
-import lombok.Builder;
-import lombok.NonNull;
-import lombok.Value;
-
-@Value
-@Builder
-public class UserDTO {
-
-    private final long id;
-    private final String email;
-    @NonNull
-    private final String name;
-    @NonNull
-    private final String avatarHash;
-    private final UserStats userStats;
-    private final Boolean isFollowedByMe;
-
-}
diff --git a/src/main/java/com/myapp/dto/UserParams.java b/src/main/java/com/myapp/dto/UserParams.java
deleted file mode 100644
index 72c5811..0000000
--- a/src/main/java/com/myapp/dto/UserParams.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.myapp.dto;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.myapp.domain.User;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-
-import javax.validation.constraints.Size;
-import java.util.Optional;
-
-@ToString
-@EqualsAndHashCode
-public final class UserParams {
-
-    @SuppressWarnings("unused")
-    private static final Logger logger = LoggerFactory.getLogger(UserParams.class);
-
-    private final String email;
-    @Size(min = 8, max = 100)
-    private final String password;
-    private final String name;
-
-    public UserParams(@JsonProperty("email") String email,
-                      @JsonProperty("password") String password,
-                      @JsonProperty("name") String name) {
-        this.email = email;
-        this.password = password;
-        this.name = name;
-    }
-
-    public Optional<String> getEmail() {
-        return Optional.ofNullable(email);
-    }
-
-    public Optional<String> getEncodedPassword() {
-        return Optional.ofNullable(password).map(p -> new BCryptPasswordEncoder().encode(p));
-    }
-
-    public Optional<String> getName() {
-        return Optional.ofNullable(name);
-    }
-
-    public User toUser() {
-        User user = new User();
-        user.setUsername(email);
-        user.setPassword(new BCryptPasswordEncoder().encode(password));
-        user.setName(name);
-        return user;
-    }
-
-}
diff --git a/src/main/java/com/myapp/repository/MicropostCustomRepository.java b/src/main/java/com/myapp/repository/MicropostCustomRepository.java
deleted file mode 100644
index 622cad8..0000000
--- a/src/main/java/com/myapp/repository/MicropostCustomRepository.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.Micropost;
-import com.myapp.domain.User;
-import com.myapp.domain.UserStats;
-import com.myapp.dto.PageParams;
-import lombok.Builder;
-import lombok.Value;
-import org.springframework.data.repository.Repository;
-
-import java.util.List;
-
-public interface MicropostCustomRepository extends Repository<Micropost, Long> {
-
-    List<Row> findAsFeed(User user, PageParams pageParams);
-
-    List<Row> findByUser(User user, PageParams pageParams);
-
-    @Value
-    @Builder
-    class Row {
-        private final Micropost micropost;
-        private final UserStats userStats;
-    }
-
-}
diff --git a/src/main/java/com/myapp/repository/MicropostCustomRepositoryImpl.java b/src/main/java/com/myapp/repository/MicropostCustomRepositoryImpl.java
deleted file mode 100644
index a22a77e..0000000
--- a/src/main/java/com/myapp/repository/MicropostCustomRepositoryImpl.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.*;
-import com.myapp.domain.QMicropost;
-import com.myapp.domain.QRelationship;
-import com.myapp.dto.PageParams;
-import com.myapp.repository.helper.UserStatsQueryHelper;
-import com.querydsl.core.types.ConstructorExpression;
-import com.querydsl.jpa.JPAExpressions;
-import com.querydsl.jpa.JPQLQuery;
-import com.querydsl.jpa.impl.JPAQueryFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Repository;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-@Repository
-public class MicropostCustomRepositoryImpl implements MicropostCustomRepository {
-
-    private final JPAQueryFactory queryFactory;
-
-    @Autowired
-    public MicropostCustomRepositoryImpl(JPAQueryFactory queryFactory) {
-        this.queryFactory = queryFactory;
-    }
-
-    @Override
-    public List<Row> findAsFeed(User user, PageParams pageParams) {
-        final QMicropost qMicropost = QMicropost.micropost;
-        final QRelationship qRelationship = QRelationship.relationship;
-
-        final ConstructorExpression<UserStats> userStatsExpression =
-                UserStatsQueryHelper.userStatsExpression(qMicropost.user);
-        final JPQLQuery<Relationship> relationshipSubQuery = JPAExpressions
-                .selectFrom(qRelationship)
-                .where(qRelationship.follower.eq(user)
-                        .and(qRelationship.followed.eq(qMicropost.user))
-                );
-        return queryFactory.select(qMicropost, qMicropost.user, userStatsExpression)
-                .from(qMicropost)
-                .innerJoin(qMicropost.user)
-                .where((qMicropost.user.eq(user).or(relationshipSubQuery.exists()))
-                        .and(pageParams.getSinceId().map(qMicropost.id::gt).orElse(null))
-                        .and(pageParams.getMaxId().map(qMicropost.id::lt).orElse(null))
-                )
-                .orderBy(qMicropost.id.desc())
-                .limit(pageParams.getCount())
-                .fetch()
-                .stream()
-                .map(tuple -> Row.builder()
-                        .micropost(tuple.get(qMicropost))
-                        .userStats(tuple.get(userStatsExpression))
-                        .build()
-                )
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public List<Row> findByUser(User user, PageParams pageParams) {
-        final QMicropost qMicropost = QMicropost.micropost;
-        return queryFactory.selectFrom(qMicropost)
-                .where(qMicropost.user.eq(user)
-                        .and(pageParams.getSinceId().map(qMicropost.id::gt).orElse(null))
-                        .and(pageParams.getMaxId().map(qMicropost.id::lt).orElse(null))
-                )
-                .orderBy(qMicropost.id.desc())
-                .limit(pageParams.getCount())
-                .fetch()
-                .stream()
-                .map(post -> Row.builder()
-                        .micropost(post)
-                        .build()
-                )
-                .collect(Collectors.toList());
-    }
-
-
-}
diff --git a/src/main/java/com/myapp/repository/MicropostRepository.java b/src/main/java/com/myapp/repository/MicropostRepository.java
deleted file mode 100644
index bda5358..0000000
--- a/src/main/java/com/myapp/repository/MicropostRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.Micropost;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface MicropostRepository extends JpaRepository<Micropost, Long> {
-}
diff --git a/src/main/java/com/myapp/repository/RelatedUserCustomRepository.java b/src/main/java/com/myapp/repository/RelatedUserCustomRepository.java
deleted file mode 100644
index 6d28313..0000000
--- a/src/main/java/com/myapp/repository/RelatedUserCustomRepository.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.Relationship;
-import com.myapp.domain.User;
-import com.myapp.dto.PageParams;
-import com.myapp.domain.UserStats;
-import lombok.Builder;
-import lombok.Value;
-import org.springframework.data.repository.Repository;
-
-import java.util.List;
-
-public interface RelatedUserCustomRepository extends Repository<User, Long> {
-
-    List<Row> findFollowings(User user, PageParams pageParams);
-
-    List<Row> findFollowers(User user, PageParams pageParams);
-
-    @Value
-    @Builder
-    class Row {
-        private final User user;
-        private final Relationship relationship;
-        private final UserStats userStats;
-    }
-}
diff --git a/src/main/java/com/myapp/repository/RelatedUserCustomRepositoryImpl.java b/src/main/java/com/myapp/repository/RelatedUserCustomRepositoryImpl.java
deleted file mode 100644
index d2c11a4..0000000
--- a/src/main/java/com/myapp/repository/RelatedUserCustomRepositoryImpl.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.QRelationship;
-import com.myapp.domain.QUser;
-import com.myapp.domain.User;
-import com.myapp.dto.PageParams;
-import com.myapp.domain.UserStats;
-import com.myapp.repository.helper.UserStatsQueryHelper;
-import com.querydsl.core.types.ConstructorExpression;
-import com.querydsl.jpa.impl.JPAQueryFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Repository;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-@Repository
-public class RelatedUserCustomRepositoryImpl implements RelatedUserCustomRepository {
-
-    private final JPAQueryFactory queryFactory;
-
-    private final QUser qUser = QUser.user;
-    private final QRelationship qRelationship = QRelationship.relationship;
-
-    @Autowired
-    public RelatedUserCustomRepositoryImpl(JPAQueryFactory queryFactory) {
-        this.queryFactory = queryFactory;
-    }
-
-    @Override
-    public List<Row> findFollowings(User subject, PageParams pageParams) {
-        final ConstructorExpression<UserStats> userStatsExpression =
-                UserStatsQueryHelper.userStatsExpression(qUser);
-
-        return queryFactory.select(qUser, qRelationship, userStatsExpression)
-                .from(qUser)
-                .innerJoin(qUser.followedRelations, qRelationship)
-                .where(qRelationship.follower.eq(subject)
-                        .and(pageParams.getSinceId().map(qRelationship.id::gt).orElse(null))
-                        .and(pageParams.getMaxId().map(qRelationship.id::lt).orElse(null))
-                )
-                .orderBy(qRelationship.id.desc())
-                .limit(pageParams.getCount())
-                .fetch()
-                .stream()
-                .map(tuple -> Row.builder()
-                        .user(tuple.get(qUser))
-                        .relationship(tuple.get(qRelationship))
-                        .userStats(tuple.get(userStatsExpression))
-                        .build()
-                )
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public List<Row> findFollowers(User subject, PageParams pageParams) {
-        final ConstructorExpression<UserStats> userStatsExpression =
-                UserStatsQueryHelper.userStatsExpression(qUser);
-
-        return queryFactory.select(qUser, qRelationship, userStatsExpression)
-                .from(qUser)
-                .innerJoin(qUser.followerRelations, qRelationship)
-                .where(qRelationship.followed.eq(subject)
-                        .and(pageParams.getSinceId().map(qRelationship.id::gt).orElse(null))
-                        .and(pageParams.getMaxId().map(qRelationship.id::lt).orElse(null))
-                )
-                .orderBy(qRelationship.id.desc())
-                .limit(pageParams.getCount())
-                .fetch()
-                .stream()
-                .map(tuple -> Row.builder()
-                        .user(tuple.get(qUser))
-                        .relationship(tuple.get(qRelationship))
-                        .userStats(tuple.get(userStatsExpression))
-                        .build()
-                )
-                .collect(Collectors.toList());
-    }
-
-}
diff --git a/src/main/java/com/myapp/repository/RelationshipRepository.java b/src/main/java/com/myapp/repository/RelationshipRepository.java
deleted file mode 100644
index c30b63e..0000000
--- a/src/main/java/com/myapp/repository/RelationshipRepository.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.Relationship;
-import com.myapp.domain.User;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.querydsl.QueryDslPredicateExecutor;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-public interface RelationshipRepository extends JpaRepository<Relationship, Long>, QueryDslPredicateExecutor<Relationship> {
-
-    Optional<Relationship> findOneByFollowerAndFollowed(User follower, User followed);
-
-    Stream<Relationship> findAllByFollowerAndFollowedIn(User follower, List<User> targets);
-
-}
diff --git a/src/main/java/com/myapp/repository/UserCustomRepository.java b/src/main/java/com/myapp/repository/UserCustomRepository.java
deleted file mode 100644
index ab3ff23..0000000
--- a/src/main/java/com/myapp/repository/UserCustomRepository.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.User;
-import com.myapp.domain.UserStats;
-import lombok.Builder;
-import lombok.Value;
-import org.springframework.data.repository.Repository;
-
-import java.util.Optional;
-
-public interface UserCustomRepository extends Repository<User, Long> {
-
-    Optional<Row> findOne(Long userId);
-
-    @Value
-    @Builder
-    class Row {
-        private final User user;
-        private final UserStats userStats;
-    }
-}
diff --git a/src/main/java/com/myapp/repository/UserCustomRepositoryImpl.java b/src/main/java/com/myapp/repository/UserCustomRepositoryImpl.java
deleted file mode 100644
index 2758ccd..0000000
--- a/src/main/java/com/myapp/repository/UserCustomRepositoryImpl.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.QUser;
-import com.myapp.domain.UserStats;
-import com.myapp.repository.helper.UserStatsQueryHelper;
-import com.querydsl.core.Tuple;
-import com.querydsl.core.types.ConstructorExpression;
-import com.querydsl.jpa.impl.JPAQueryFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Repository;
-
-import java.util.Optional;
-
-@Repository
-class UserCustomRepositoryImpl implements UserCustomRepository {
-
-    private final JPAQueryFactory queryFactory;
-
-    private final QUser qUser = QUser.user;
-
-    @Autowired
-    public UserCustomRepositoryImpl(JPAQueryFactory queryFactory) {
-        this.queryFactory = queryFactory;
-    }
-
-    @Override
-    public Optional<Row> findOne(Long userId) {
-        final ConstructorExpression<UserStats> userStatsExpression =
-                UserStatsQueryHelper.userStatsExpression(qUser);
-        final Tuple row = queryFactory.select(qUser, userStatsExpression)
-                .from(qUser)
-                .where(qUser.id.eq(userId))
-                .fetchOne();
-        return Optional.ofNullable(row).map(tuple ->
-                Row.builder()
-                        .user(tuple.get(qUser))
-                        .userStats(tuple.get(userStatsExpression))
-                        .build()
-        );
-    }
-
-
-}
diff --git a/src/main/java/com/myapp/repository/UserRepository.java b/src/main/java/com/myapp/repository/UserRepository.java
deleted file mode 100644
index d518556..0000000
--- a/src/main/java/com/myapp/repository/UserRepository.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.myapp.repository;
-
-import com.myapp.domain.User;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
-
-import java.util.List;
-import java.util.Optional;
-
-public interface UserRepository extends JpaRepository<User, Long> {
-
-    Optional<User> findOneByUsername(String username);
-
-}
diff --git a/src/main/java/com/myapp/repository/helper/UserStatsQueryHelper.java b/src/main/java/com/myapp/repository/helper/UserStatsQueryHelper.java
deleted file mode 100644
index 4dd9959..0000000
--- a/src/main/java/com/myapp/repository/helper/UserStatsQueryHelper.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.myapp.repository.helper;
-
-import com.myapp.domain.QMicropost;
-import com.myapp.domain.QRelationship;
-import com.myapp.domain.QUser;
-import com.myapp.domain.UserStats;
-import com.querydsl.core.types.ConstructorExpression;
-import com.querydsl.core.types.Projections;
-import com.querydsl.jpa.JPAExpressions;
-import com.querydsl.jpa.JPQLQuery;
-
-public class UserStatsQueryHelper {
-
-    public static ConstructorExpression<UserStats> userStatsExpression(QUser qUser) {
-        return Projections.constructor(UserStats.class,
-                cntPostsQuery(qUser),
-                cntFollowingsQuery(qUser),
-                cntFollowersQuery(qUser)
-        );
-    }
-
-    private static JPQLQuery<Long> cntFollowersQuery(QUser qUser) {
-        final QRelationship qRelationship = new QRelationship("relationship_cnt_followers");
-        return JPAExpressions.select(qRelationship.count())
-                .from(qRelationship)
-                .where(qRelationship.followed.eq(qUser));
-    }
-
-    private static JPQLQuery<Long> cntFollowingsQuery(QUser qUser) {
-        final QRelationship qRelationship = new QRelationship("relationship_cnt_followings");
-        return JPAExpressions.select(qRelationship.count())
-                .from(qRelationship)
-                .where(qRelationship.follower.eq(qUser));
-    }
-
-    private static JPQLQuery<Long> cntPostsQuery(QUser qUser) {
-        final QMicropost qMicropost = new QMicropost("micropost_cnt_posts");
-        return JPAExpressions.select(qMicropost.count())
-                .from(qMicropost)
-                .where(qMicropost.user.eq(qUser));
-    }
-}
diff --git a/src/main/java/com/myapp/service/MicropostService.java b/src/main/java/com/myapp/service/MicropostService.java
deleted file mode 100644
index f30628a..0000000
--- a/src/main/java/com/myapp/service/MicropostService.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.domain.Micropost;
-import com.myapp.domain.User;
-import com.myapp.dto.PageParams;
-import com.myapp.dto.PostDTO;
-import com.myapp.service.exceptions.NotPermittedException;
-
-import java.util.List;
-
-public interface MicropostService {
-
-    void delete(Long id) throws NotPermittedException;
-
-    List<PostDTO> findAsFeed(PageParams pageParams);
-
-    List<PostDTO> findByUser(Long userId, PageParams pageParams);
-
-    List<PostDTO> findMyPosts(PageParams pageParams);
-
-    Micropost saveMyPost(Micropost post);
-
-}
diff --git a/src/main/java/com/myapp/service/MicropostServiceImpl.java b/src/main/java/com/myapp/service/MicropostServiceImpl.java
deleted file mode 100644
index 10770ac..0000000
--- a/src/main/java/com/myapp/service/MicropostServiceImpl.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.domain.Micropost;
-import com.myapp.domain.Relationship;
-import com.myapp.domain.User;
-import com.myapp.dto.PageParams;
-import com.myapp.dto.PostDTO;
-import com.myapp.repository.MicropostCustomRepository;
-import com.myapp.repository.MicropostRepository;
-import com.myapp.repository.RelationshipRepository;
-import com.myapp.repository.UserRepository;
-import com.myapp.service.exceptions.NotPermittedException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.AccessDeniedException;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-@Service
-@Transactional
-public class MicropostServiceImpl implements MicropostService {
-
-    private final MicropostRepository micropostRepository;
-    private final UserRepository userRepository;
-    private final MicropostCustomRepository micropostCustomRepository;
-    private final RelationshipRepository relationshipRepository;
-    private final SecurityContextService securityContextService;
-
-    @Autowired
-    public MicropostServiceImpl(MicropostRepository micropostRepository, UserRepository userRepository, MicropostCustomRepository micropostCustomRepository, RelationshipRepository relationshipRepository, SecurityContextService securityContextService) {
-        this.micropostRepository = micropostRepository;
-        this.userRepository = userRepository;
-        this.micropostCustomRepository = micropostCustomRepository;
-        this.relationshipRepository = relationshipRepository;
-        this.securityContextService = securityContextService;
-    }
-
-    @Override
-    public void delete(Long id) throws NotPermittedException {
-        final Micropost micropost = micropostRepository.findOne(id);
-        final Optional<User> currentUser = securityContextService.currentUser();
-
-        currentUser.filter(u -> u.equals(micropost.getUser()))
-                .ifPresent(u -> micropostRepository.delete(id));
-        currentUser.filter(u -> u.equals(micropost.getUser()))
-                .orElseThrow(() -> new NotPermittedException("no permission to delete this post"));
-    }
-
-    @Override
-    public List<PostDTO> findAsFeed(PageParams pageParams) {
-        return securityContextService.currentUser()
-                .map(u -> {
-                    final List<MicropostCustomRepository.Row> rows = micropostCustomRepository.findAsFeed(u, pageParams);
-                    final List<User> relatedUsers = rows.stream()
-                            .map(row -> row.getMicropost().getUser())
-                            .collect(Collectors.toList());
-                    return rows.stream()
-                            .map(toDTO(relatedUsers))
-                            .collect(Collectors.toList());
-                })
-                .orElseThrow(() -> new AccessDeniedException(""));
-    }
-
-    @Override
-    public List<PostDTO> findByUser(Long userId, PageParams pageParams) {
-        final User user = userRepository.findOne(userId);
-        final List<User> relatedUsers = Collections.singletonList(user);
-        return micropostCustomRepository.findByUser(user, pageParams)
-                .stream()
-                .map(toDTO(relatedUsers))
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public List<PostDTO> findMyPosts(PageParams pageParams) {
-        return securityContextService.currentUser()
-                .map(u -> findByUser(u.getId(), pageParams))
-                .orElseThrow(RuntimeException::new);
-    }
-
-    @Override
-    public Micropost saveMyPost(Micropost post) {
-        return securityContextService.currentUser()
-                .map(u -> {
-                    post.setUser(u);
-                    return micropostRepository.save(post);
-                })
-                .orElseThrow(() -> new AccessDeniedException(""));
-    }
-
-    private Function<MicropostCustomRepository.Row, PostDTO> toDTO(List<User> relatedUsers) {
-        final Optional<User> currentUser = securityContextService.currentUser();
-        final List<User> followedByMe = currentUser.map(u -> relationshipRepository
-                .findAllByFollowerAndFollowedIn(u, relatedUsers)
-                .map(Relationship::getFollowed)
-                .collect(Collectors.toList())
-        ).orElse(Collections.emptyList());
-
-        return r -> {
-            final Boolean isMyPost = currentUser
-                    .map(u -> r.getMicropost().getUser().equals(u))
-                    .orElse(null);
-            final Boolean isFollowedByMe = currentUser
-                    .map(u -> followedByMe.contains(r.getMicropost().getUser()))
-                    .orElse(null);
-            return PostDTO.newInstance(r.getMicropost(), r.getUserStats(), isMyPost, isFollowedByMe);
-        };
-    }
-
-}
diff --git a/src/main/java/com/myapp/service/RelationshipService.java b/src/main/java/com/myapp/service/RelationshipService.java
deleted file mode 100644
index 7112bd4..0000000
--- a/src/main/java/com/myapp/service/RelationshipService.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.dto.PageParams;
-import com.myapp.dto.RelatedUserDTO;
-import com.myapp.service.exceptions.RelationshipNotFoundException;
-
-import java.util.List;
-
-public interface RelationshipService {
-
-    List<RelatedUserDTO> findFollowings(Long userId, PageParams pageParams);
-
-    List<RelatedUserDTO> findFollowers(Long userId, PageParams pageParams);
-
-    void follow(Long userId);
-
-    void unfollow(Long userId) throws RelationshipNotFoundException;
-
-}
diff --git a/src/main/java/com/myapp/service/RelationshipServiceImpl.java b/src/main/java/com/myapp/service/RelationshipServiceImpl.java
deleted file mode 100644
index 41f986c..0000000
--- a/src/main/java/com/myapp/service/RelationshipServiceImpl.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.Utils;
-import com.myapp.domain.Relationship;
-import com.myapp.domain.User;
-import com.myapp.dto.PageParams;
-import com.myapp.dto.RelatedUserDTO;
-import com.myapp.repository.RelatedUserCustomRepository;
-import com.myapp.repository.RelationshipRepository;
-import com.myapp.repository.UserRepository;
-import com.myapp.service.exceptions.RelationshipNotFoundException;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-@Service
-@Transactional
-public class RelationshipServiceImpl implements RelationshipService {
-
-    private final RelationshipRepository relationshipRepository;
-    private final RelatedUserCustomRepository relatedUserCustomRepository;
-    private final UserRepository userRepository;
-    private final SecurityContextService securityContextService;
-
-    public RelationshipServiceImpl(RelationshipRepository relationshipRepository,
-                                   RelatedUserCustomRepository relatedUserCustomRepository,
-                                   UserRepository userRepository,
-                                   SecurityContextService securityContextService) {
-        this.relationshipRepository = relationshipRepository;
-        this.relatedUserCustomRepository = relatedUserCustomRepository;
-        this.userRepository = userRepository;
-        this.securityContextService = securityContextService;
-    }
-
-    @Override
-    public List<RelatedUserDTO> findFollowings(Long userId, PageParams pageParams) {
-        final User user = userRepository.findOne(userId);
-        final List<RelatedUserCustomRepository.Row> rows = relatedUserCustomRepository.findFollowings(user, pageParams);
-
-        return rowsToRelatedUsers(rows);
-    }
-
-    @Override
-    public List<RelatedUserDTO> findFollowers(Long userId, PageParams pageParams) {
-        final User user = userRepository.findOne(userId);
-        final List<RelatedUserCustomRepository.Row> rows = relatedUserCustomRepository.findFollowers(user, pageParams);
-
-        return rowsToRelatedUsers(rows);
-    }
-
-    @Override
-    public void follow(Long userId) {
-        final User user = userRepository.findOne(userId);
-        securityContextService.currentUser().ifPresent(currentUser -> {
-            final Relationship relationship = new Relationship(currentUser, user);
-            relationshipRepository.save(relationship);
-        });
-    }
-
-    @Override
-    public void unfollow(Long userId) throws RelationshipNotFoundException {
-        final User followed = userRepository.findOne(userId);
-        final Optional<Relationship> relationship = securityContextService.currentUser()
-                .flatMap(currentUser -> relationshipRepository.findOneByFollowerAndFollowed(currentUser, followed));
-
-        relationship.ifPresent(relationshipRepository::delete);
-        relationship.orElseThrow(RelationshipNotFoundException::new);
-    }
-
-    private List<RelatedUserDTO> rowsToRelatedUsers(List<RelatedUserCustomRepository.Row> rows) {
-        final Optional<User> currentUser = securityContextService.currentUser();
-
-        final List<User> relatedUsers = rows.stream()
-                .map(RelatedUserCustomRepository.Row::getUser)
-                .collect(Collectors.toList());
-
-        final List<User> followedByMe = currentUser.map(u -> relationshipRepository
-                .findAllByFollowerAndFollowedIn(u, relatedUsers)
-                .map(Relationship::getFollowed)
-                .collect(Collectors.toList())
-        ).orElse(Collections.emptyList());
-
-        return rows.stream().map(r -> {
-            final Boolean isFollowedByMe = currentUser
-                    .map(u -> followedByMe.contains(r.getUser()))
-                    .orElse(null);
-            return RelatedUserDTO.builder()
-                    .id(r.getUser().getId())
-                    .avatarHash(Utils.md5(r.getUser().getUsername()))
-                    .name(r.getUser().getName())
-                    .userStats(r.getUserStats())
-                    .relationshipId(r.getRelationship().getId())
-                    .isFollowedByMe(isFollowedByMe)
-                    .build();
-        }).collect(Collectors.toList());
-    }
-
-}
diff --git a/src/main/java/com/myapp/service/SecurityContextService.java b/src/main/java/com/myapp/service/SecurityContextService.java
deleted file mode 100644
index 0e131a0..0000000
--- a/src/main/java/com/myapp/service/SecurityContextService.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.domain.User;
-
-import java.util.Optional;
-
-public interface SecurityContextService {
-    Optional<User> currentUser();
-}
diff --git a/src/main/java/com/myapp/service/SecurityContextServiceImpl.java b/src/main/java/com/myapp/service/SecurityContextServiceImpl.java
deleted file mode 100644
index 9e4d672..0000000
--- a/src/main/java/com/myapp/service/SecurityContextServiceImpl.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.domain.User;
-import com.myapp.repository.UserRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.Optional;
-
-@Service
-@Transactional
-public class SecurityContextServiceImpl implements SecurityContextService {
-
-    private final UserRepository userRepository;
-
-    @Autowired
-    public SecurityContextServiceImpl(UserRepository userRepository) {
-        this.userRepository = userRepository;
-    }
-
-    @Override
-    public Optional<User> currentUser() {
-        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-        return userRepository.findOneByUsername(authentication.getName());
-    }
-}
diff --git a/src/main/java/com/myapp/service/UserService.java b/src/main/java/com/myapp/service/UserService.java
deleted file mode 100644
index acba3b7..0000000
--- a/src/main/java/com/myapp/service/UserService.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.domain.User;
-import com.myapp.dto.PageParams;
-import com.myapp.dto.RelatedUserDTO;
-import com.myapp.dto.UserDTO;
-import com.myapp.dto.UserParams;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
-
-import java.util.List;
-import java.util.Optional;
-
-public interface UserService extends org.springframework.security.core.userdetails.UserDetailsService {
-
-    Optional<UserDTO> findOne(Long id);
-
-    Optional<UserDTO> findMe();
-
-    Page<UserDTO> findAll(PageRequest pageable);
-
-    User create(UserParams params);
-
-    User update(User user, UserParams params);
-
-    User updateMe(UserParams params);
-
-
-}
diff --git a/src/main/java/com/myapp/service/UserServiceImpl.java b/src/main/java/com/myapp/service/UserServiceImpl.java
deleted file mode 100644
index 037c589..0000000
--- a/src/main/java/com/myapp/service/UserServiceImpl.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.myapp.service;
-
-import com.myapp.Utils;
-import com.myapp.domain.User;
-import com.myapp.dto.UserDTO;
-import com.myapp.dto.UserParams;
-import com.myapp.repository.RelationshipRepository;
-import com.myapp.repository.UserCustomRepository;
-import com.myapp.repository.UserRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.security.access.AccessDeniedException;
-import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.Optional;
-
-@Service("userService")
-@Transactional
-public class UserServiceImpl implements UserService {
-
-    private final UserRepository userRepository;
-    private final UserCustomRepository userCustomRepository;
-    private final RelationshipRepository relationshipRepository;
-    private final SecurityContextService securityContextService;
-
-    @Autowired
-    public UserServiceImpl(UserRepository userRepository,
-                           UserCustomRepository userCustomRepository,
-                           RelationshipRepository relationshipRepository,
-                           SecurityContextService securityContextService) {
-        this.userRepository = userRepository;
-        this.userCustomRepository = userCustomRepository;
-        this.relationshipRepository = relationshipRepository;
-        this.securityContextService = securityContextService;
-    }
-
-    @Override
-    public Optional<UserDTO> findOne(Long id) {
-        return userCustomRepository.findOne(id).map(r -> {
-            final Optional<User> currentUser = securityContextService.currentUser();
-            final Boolean isFollowedByMe = currentUser
-                    .map(u -> relationshipRepository
-                            .findOneByFollowerAndFollowed(u, r.getUser())
-                            .isPresent()
-                    )
-                    .orElse(null);
-            // Set email only if it equals with currentUser.
-            final String email = currentUser
-                    .filter(u -> u.equals(r.getUser()))
-                    .map(User::getUsername)
-                    .orElse(null);
-
-            return UserDTO.builder()
-                    .id(r.getUser().getId())
-                    .email(email)
-                    .avatarHash(Utils.md5(r.getUser().getUsername()))
-                    .name(r.getUser().getName())
-                    .userStats(r.getUserStats())
-                    .isFollowedByMe(isFollowedByMe)
-                    .build();
-        });
-    }
-
-    @Override
-    public Optional<UserDTO> findMe() {
-        return securityContextService.currentUser().flatMap(u -> findOne(u.getId()));
-    }
-
-    @Override
-    public Page<UserDTO> findAll(PageRequest pageable) {
-        return userRepository.findAll(pageable).map(u -> UserDTO.builder()
-                .id(u.getId())
-                .name(u.getName())
-                .avatarHash(Utils.md5(u.getUsername()))
-                .build()
-        );
-    }
-
-    @Override
-    public User create(UserParams params) {
-        return userRepository.save(params.toUser());
-    }
-
-    @Override
-    public User update(User user, UserParams params) {
-        params.getEmail().ifPresent(user::setUsername);
-        params.getEncodedPassword().ifPresent(user::setPassword);
-        params.getName().ifPresent(user::setName);
-        return userRepository.save(user);
-    }
-
-    @Override
-    public User updateMe(UserParams params) {
-        return securityContextService.currentUser()
-                .map(u -> update(u, params))
-                .orElseThrow(() -> new AccessDeniedException(""));
-    }
-
-    @Override
-    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-        final Optional<User> user = userRepository.findOneByUsername(username);
-        final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker();
-        user.ifPresent(detailsChecker::check);
-        return user.orElseThrow(() -> new UsernameNotFoundException("user not found."));
-    }
-
-}
diff --git a/src/main/java/com/myapp/service/exceptions/NotPermittedException.java b/src/main/java/com/myapp/service/exceptions/NotPermittedException.java
deleted file mode 100644
index 53a5fa8..0000000
--- a/src/main/java/com/myapp/service/exceptions/NotPermittedException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.myapp.service.exceptions;
-
-public class NotPermittedException extends Exception {
-
-    public NotPermittedException(String message) {
-        super(message);
-    }
-
-}
diff --git a/src/main/java/com/myapp/service/exceptions/RelationshipNotFoundException.java b/src/main/java/com/myapp/service/exceptions/RelationshipNotFoundException.java
deleted file mode 100644
index 6c00c91..0000000
--- a/src/main/java/com/myapp/service/exceptions/RelationshipNotFoundException.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.myapp.service.exceptions;
-
-public class RelationshipNotFoundException extends Exception {
-}
diff --git a/src/main/java/com/myapp/service/exceptions/UserNotFoundException.java b/src/main/java/com/myapp/service/exceptions/UserNotFoundException.java
deleted file mode 100644
index c2e5edc..0000000
--- a/src/main/java/com/myapp/service/exceptions/UserNotFoundException.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.myapp.service.exceptions;
-
-public class UserNotFoundException extends Exception {
-}
diff --git a/src/main/kotlin/com/myapp/Application.kt b/src/main/kotlin/com/myapp/Application.kt
new file mode 100644
index 0000000..2215c0d
--- /dev/null
+++ b/src/main/kotlin/com/myapp/Application.kt
@@ -0,0 +1,13 @@
+package com.myapp
+
+import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties
+import org.springframework.boot.SpringApplication
+import org.springframework.boot.autoconfigure.SpringBootApplication
+
+@SpringBootApplication
+@EnableEncryptableProperties
+class Application
+
+fun main(args: Array<String>) {
+    SpringApplication.run(Application::class.java, *args)
+}
diff --git a/src/main/kotlin/com/myapp/Utils.kt b/src/main/kotlin/com/myapp/Utils.kt
new file mode 100644
index 0000000..a2e5909
--- /dev/null
+++ b/src/main/kotlin/com/myapp/Utils.kt
@@ -0,0 +1,10 @@
+package com.myapp
+
+import java.security.MessageDigest
+import javax.xml.bind.DatatypeConverter
+
+fun String.md5(): String {
+    val digester = MessageDigest.getInstance("MD5")
+    val bytes = digester.digest(toByteArray())
+    return DatatypeConverter.printHexBinary(bytes).toLowerCase()
+}
diff --git a/src/main/kotlin/com/myapp/auth/SecurityConfig.kt b/src/main/kotlin/com/myapp/auth/SecurityConfig.kt
new file mode 100644
index 0000000..990bb37
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/SecurityConfig.kt
@@ -0,0 +1,77 @@
+package com.myapp.auth
+
+import org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint
+import org.springframework.boot.web.servlet.FilterRegistrationBean
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.core.annotation.Order
+import org.springframework.http.HttpMethod
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+
+
+@Configuration
+@EnableWebSecurity
+@Order(1)
+class SecurityConfig(
+    private val userDetailsService: UserDetailsService,
+    private val statelessAuthenticationFilter: StatelessAuthenticationFilter
+) : WebSecurityConfigurerAdapter(true) {
+
+    override fun configure(http: HttpSecurity) {
+        // we use jwt so that we can disable csrf protection
+        http.csrf().disable()
+
+        http
+            .exceptionHandling().and()
+            .anonymous().and()
+            .servletApi().and()
+            .headers().cacheControl()
+
+        http.authorizeRequests()
+            .antMatchers(HttpMethod.GET, "/api/users").hasRole("USER")
+            .antMatchers(HttpMethod.GET, "/api/users/me").hasRole("USER")
+            .antMatchers(HttpMethod.PATCH, "/api/users/me").hasRole("USER")
+            .antMatchers(HttpMethod.GET, "/api/users/me/microposts").hasRole("USER")
+            .antMatchers(HttpMethod.POST, "/api/microposts/**").hasRole("USER")
+            .antMatchers(HttpMethod.DELETE, "/api/microposts/**").hasRole("USER")
+            .antMatchers(HttpMethod.POST, "/api/relationships/**").hasRole("USER")
+            .antMatchers(HttpMethod.DELETE, "/api/relationships/**").hasRole("USER")
+            .antMatchers(HttpMethod.GET, "/api/feed").hasRole("USER")
+            .and()
+            .exceptionHandling()
+            .authenticationEntryPoint(Http401AuthenticationEntryPoint("'Bearer token_type=\"JWT\"'"))
+
+        http.addFilterBefore(statelessAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
+    }
+
+    @Bean
+    override fun authenticationManagerBean(): AuthenticationManager =
+        super.authenticationManagerBean()
+
+    /**
+     * Prevent StatelessAuthenticationFilter will be added to Spring Boot filter chain.
+     * Only Spring Security must use it.
+     */
+    @Bean
+    fun registration(filter: StatelessAuthenticationFilter): FilterRegistrationBean {
+        return FilterRegistrationBean(filter).apply {
+            isEnabled = false
+        }
+    }
+
+    override fun configure(auth: AuthenticationManagerBuilder) {
+        auth.userDetailsService(userDetailsService).passwordEncoder(BCryptPasswordEncoder())
+    }
+
+    override fun userDetailsService() = userDetailsService
+
+}
+
+
diff --git a/src/main/kotlin/com/myapp/auth/SecurityContextService.kt b/src/main/kotlin/com/myapp/auth/SecurityContextService.kt
new file mode 100644
index 0000000..8e069a3
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/SecurityContextService.kt
@@ -0,0 +1,7 @@
+package com.myapp.auth
+
+import com.myapp.domain.User
+
+interface SecurityContextService {
+    fun currentUser(): User?
+}
diff --git a/src/main/kotlin/com/myapp/auth/SecurityContextServiceImpl.kt b/src/main/kotlin/com/myapp/auth/SecurityContextServiceImpl.kt
new file mode 100644
index 0000000..e24aa9f
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/SecurityContextServiceImpl.kt
@@ -0,0 +1,28 @@
+package com.myapp.auth
+
+import com.myapp.domain.User
+import com.myapp.domain.UserDetailsImpl
+import org.slf4j.LoggerFactory
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.stereotype.Service
+
+@Service
+class SecurityContextServiceImpl : SecurityContextService {
+
+    @Suppress("unused")
+    private val logger = LoggerFactory.getLogger(SecurityContextServiceImpl::class.java)
+
+    override fun currentUser(): User? {
+        return SecurityContextHolder
+            .getContext()
+            .authentication
+            .principal
+            .let {
+                when (it) {
+                    is UserDetailsImpl -> it.user
+                    else -> null
+                }
+            }
+    }
+
+}
diff --git a/src/main/kotlin/com/myapp/auth/StatelessAuthenticationFilter.kt b/src/main/kotlin/com/myapp/auth/StatelessAuthenticationFilter.kt
new file mode 100644
index 0000000..41d17d5
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/StatelessAuthenticationFilter.kt
@@ -0,0 +1,34 @@
+package com.myapp.auth
+
+import io.jsonwebtoken.JwtException
+import org.springframework.security.core.AuthenticationException
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.stereotype.Component
+import org.springframework.web.filter.GenericFilterBean
+import javax.servlet.FilterChain
+import javax.servlet.ServletRequest
+import javax.servlet.ServletResponse
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+@Component
+class StatelessAuthenticationFilter(
+    private val tokenAuthenticationService: TokenAuthenticationService
+) : GenericFilterBean() {
+
+    override fun doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) {
+        try {
+            val authentication = tokenAuthenticationService.authentication(req as HttpServletRequest)
+            SecurityContextHolder.getContext().authentication = authentication
+            chain.doFilter(req, res)
+            SecurityContextHolder.getContext().authentication = null
+        } catch (e: AuthenticationException) {
+            SecurityContextHolder.clearContext()
+            (res as HttpServletResponse).status = HttpServletResponse.SC_UNAUTHORIZED
+        } catch (e: JwtException) {
+            SecurityContextHolder.clearContext()
+            (res as HttpServletResponse).status = HttpServletResponse.SC_UNAUTHORIZED
+        }
+    }
+
+}
diff --git a/src/main/kotlin/com/myapp/auth/TokenAuthenticationService.kt b/src/main/kotlin/com/myapp/auth/TokenAuthenticationService.kt
new file mode 100644
index 0000000..4a6edb3
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/TokenAuthenticationService.kt
@@ -0,0 +1,11 @@
+package com.myapp.auth
+
+import org.springframework.security.core.Authentication
+
+import javax.servlet.http.HttpServletRequest
+
+interface TokenAuthenticationService {
+
+    fun authentication(request: HttpServletRequest): Authentication?
+}
+
diff --git a/src/main/kotlin/com/myapp/auth/TokenAuthenticationServiceImpl.kt b/src/main/kotlin/com/myapp/auth/TokenAuthenticationServiceImpl.kt
new file mode 100644
index 0000000..4e7131a
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/TokenAuthenticationServiceImpl.kt
@@ -0,0 +1,24 @@
+package com.myapp.auth
+
+import org.springframework.security.core.Authentication
+import org.springframework.stereotype.Service
+import javax.servlet.http.HttpServletRequest
+
+@Service
+class TokenAuthenticationServiceImpl(
+    private val tokenHandler: TokenHandler
+) : TokenAuthenticationService {
+
+    override fun authentication(request: HttpServletRequest): Authentication? {
+        val authHeader = request.getHeader("authorization") ?: return null
+        if (!authHeader.startsWith("Bearer")) return null
+
+        val jwt = authHeader.substring(7)
+        if (jwt.isEmpty()) return null
+
+        return tokenHandler
+            .parseUserFromToken(jwt)
+            .let(::UserAuthentication)
+    }
+}
+
diff --git a/src/main/kotlin/com/myapp/auth/TokenHandler.kt b/src/main/kotlin/com/myapp/auth/TokenHandler.kt
new file mode 100644
index 0000000..845a2e2
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/TokenHandler.kt
@@ -0,0 +1,13 @@
+package com.myapp.auth
+
+import com.myapp.domain.User
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.stereotype.Component
+
+@Component
+interface TokenHandler {
+
+    fun parseUserFromToken(token: String): UserDetails
+    fun createTokenForUser(user: User): String
+
+}
diff --git a/src/main/kotlin/com/myapp/auth/TokenHandlerImpl.kt b/src/main/kotlin/com/myapp/auth/TokenHandlerImpl.kt
new file mode 100644
index 0000000..f96f6d6
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/TokenHandlerImpl.kt
@@ -0,0 +1,43 @@
+package com.myapp.auth
+
+import com.myapp.domain.User
+import com.myapp.domain.UserDetailsImpl
+import com.myapp.repository.UserRepository
+import io.jsonwebtoken.Jwts
+import io.jsonwebtoken.SignatureAlgorithm
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.stereotype.Component
+import java.time.ZonedDateTime
+import java.util.*
+
+@Component
+class TokenHandlerImpl(
+    @param:Value("\${app.jwt.secret}")
+    private val secret: String,
+    private val userRepository: UserRepository
+) : TokenHandler {
+
+    override fun parseUserFromToken(token: String): UserDetails {
+        val userId = Jwts.parser()
+            .setSigningKey(secret)
+            .parseClaimsJws(token)
+            .body
+            .subject
+            .toLong()
+
+        return userRepository.findOne(userId).let(::UserDetailsImpl)
+    }
+
+    override fun createTokenForUser(user: User): String {
+        val afterOneWeek = ZonedDateTime.now().plusWeeks(1)
+
+        return Jwts.builder()
+            .setSubject(user.id.toString())
+            .signWith(SignatureAlgorithm.HS512, secret)
+            .setExpiration(Date.from(afterOneWeek.toInstant()))
+            .compact()
+    }
+
+}
+
diff --git a/src/main/kotlin/com/myapp/auth/UserAuthentication.kt b/src/main/kotlin/com/myapp/auth/UserAuthentication.kt
new file mode 100644
index 0000000..72fccf7
--- /dev/null
+++ b/src/main/kotlin/com/myapp/auth/UserAuthentication.kt
@@ -0,0 +1,23 @@
+package com.myapp.auth
+
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+
+class UserAuthentication(
+    private val user: UserDetails
+) : Authentication {
+    private var authenticated = true
+
+    override fun getAuthorities(): Collection<GrantedAuthority> = user.authorities
+    override fun getCredentials(): Any = user.password
+    override fun getDetails(): Any? = null
+    override fun getPrincipal(): UserDetails = user
+    override fun isAuthenticated(): Boolean = authenticated
+    override fun getName(): String = user.username
+
+    override fun setAuthenticated(authenticated: Boolean) {
+        this.authenticated = authenticated
+    }
+
+}
diff --git a/src/main/kotlin/com/myapp/config/Swagger2Config.kt b/src/main/kotlin/com/myapp/config/Swagger2Config.kt
new file mode 100644
index 0000000..79704dc
--- /dev/null
+++ b/src/main/kotlin/com/myapp/config/Swagger2Config.kt
@@ -0,0 +1,25 @@
+package com.myapp.config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Profile
+import springfox.documentation.spi.DocumentationType
+import springfox.documentation.spring.web.plugins.Docket
+import springfox.documentation.swagger2.annotations.EnableSwagger2
+
+import springfox.documentation.builders.PathSelectors.regex
+
+@Configuration
+@Profile("dev")
+@EnableSwagger2
+class Swagger2Config {
+
+    @Bean
+    fun swaggerSpringMvcPlugin(): Docket {
+        return Docket(DocumentationType.SWAGGER_2)
+            .select()
+            .paths(regex("/api.*"))
+            .build()
+    }
+
+}
diff --git a/src/main/kotlin/com/myapp/controller/AuthController.kt b/src/main/kotlin/com/myapp/controller/AuthController.kt
new file mode 100644
index 0000000..8a66f12
--- /dev/null
+++ b/src/main/kotlin/com/myapp/controller/AuthController.kt
@@ -0,0 +1,44 @@
+package com.myapp.controller
+
+import com.myapp.auth.TokenHandler
+import com.myapp.auth.SecurityContextService
+import org.slf4j.LoggerFactory
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+
+@RestController
+@RequestMapping("/api/auth")
+class AuthController(
+    private val authenticationManager: AuthenticationManager,
+    private val tokenHandler: TokenHandler,
+    private val securityContextService: SecurityContextService
+) {
+
+    @Suppress("unused")
+    private val logger = LoggerFactory.getLogger(AuthController::class.java)
+
+    @PostMapping
+    fun auth(@RequestBody params: AuthParams): AuthResponse {
+        val loginToken = UsernamePasswordAuthenticationToken(params.email, params.password)
+        val authentication = authenticationManager.authenticate(loginToken)
+        SecurityContextHolder.getContext().authentication = authentication
+
+        return securityContextService.currentUser()
+            .let { requireNotNull(it) }
+            .let { tokenHandler.createTokenForUser(it).let(::AuthResponse) }
+    }
+
+    data class AuthParams(
+        val email: String,
+        val password: String
+    )
+
+    data class AuthResponse(val token: String)
+
+}
diff --git a/src/main/kotlin/com/myapp/controller/FeedController.kt b/src/main/kotlin/com/myapp/controller/FeedController.kt
new file mode 100644
index 0000000..edf85a3
--- /dev/null
+++ b/src/main/kotlin/com/myapp/controller/FeedController.kt
@@ -0,0 +1,30 @@
+package com.myapp.controller
+
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+import com.myapp.service.FeedService
+import org.slf4j.LoggerFactory
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/api/feed")
+class FeedController(
+    private val feedService: FeedService
+) {
+
+    @Suppress("unused")
+    private val logger = LoggerFactory.getLogger(FeedController::class.java)
+
+    @GetMapping
+    fun feed(
+        @RequestParam(required = false) sinceId: Long?,
+        @RequestParam(required = false) maxId: Long?,
+        @RequestParam(required = false, defaultValue = "20") count: Int
+    ): List<Micropost> {
+        return feedService.findFeed(PageParams(sinceId, maxId, count))
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/controller/MicropostController.kt b/src/main/kotlin/com/myapp/controller/MicropostController.kt
new file mode 100644
index 0000000..1fe02d9
--- /dev/null
+++ b/src/main/kotlin/com/myapp/controller/MicropostController.kt
@@ -0,0 +1,34 @@
+package com.myapp.controller
+
+import com.myapp.domain.Micropost
+import com.myapp.service.MicropostService
+import com.myapp.service.exception.NotAuthorizedException
+import org.springframework.http.HttpStatus
+import org.springframework.web.bind.annotation.*
+
+
+@RestController
+@RequestMapping("/api/microposts")
+class MicropostController(
+    private val micropostService: MicropostService
+) {
+
+    @PostMapping
+    fun create(@RequestBody params: MicropostParams): Micropost {
+        return micropostService.create(params.content)
+    }
+
+    @DeleteMapping(value = "{id}")
+    fun delete(@PathVariable("id") id: Long) {
+        micropostService.delete(id)
+    }
+
+    @ResponseStatus(value = HttpStatus.FORBIDDEN)
+    @ExceptionHandler(NotAuthorizedException::class)
+    fun handleNotAuthorized() = Unit
+
+    data class MicropostParams(val content: String)
+
+}
+
+
diff --git a/src/main/kotlin/com/myapp/controller/RelatedUserController.kt b/src/main/kotlin/com/myapp/controller/RelatedUserController.kt
new file mode 100644
index 0000000..a2c1086
--- /dev/null
+++ b/src/main/kotlin/com/myapp/controller/RelatedUserController.kt
@@ -0,0 +1,36 @@
+package com.myapp.controller
+
+import com.myapp.domain.RelatedUser
+import com.myapp.dto.request.PageParams
+import com.myapp.service.RelatedUserService
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/api/users/{userId}")
+class RelatedUserController(
+    private val relatedUserService: RelatedUserService
+) {
+
+    @GetMapping(path = arrayOf("/followings"))
+    fun followings(
+        @PathVariable("userId") userId: Long,
+        @RequestParam(required = false) sinceId: Long?,
+        @RequestParam(required = false) maxId: Long?,
+        @RequestParam(required = false, defaultValue = "20") count: Int
+    ): List<RelatedUser> {
+        val pageParams = PageParams(sinceId, maxId, count)
+        return relatedUserService.findFollowings(userId, pageParams)
+    }
+
+    @GetMapping(path = arrayOf("/followers"))
+    fun followers(
+        @PathVariable("userId") userId: Long,
+        @RequestParam(required = false) sinceId: Long?,
+        @RequestParam(required = false) maxId: Long?,
+        @RequestParam(required = false, defaultValue = "20") count: Int
+    ): List<RelatedUser> {
+        val pageParams = PageParams(sinceId, maxId, count)
+        return relatedUserService.findFollowers(userId, pageParams)
+    }
+
+}
diff --git a/src/main/kotlin/com/myapp/controller/RelationshipController.kt b/src/main/kotlin/com/myapp/controller/RelationshipController.kt
new file mode 100644
index 0000000..31f5a9d
--- /dev/null
+++ b/src/main/kotlin/com/myapp/controller/RelationshipController.kt
@@ -0,0 +1,35 @@
+package com.myapp.controller
+
+import com.myapp.repository.exception.RelationshipDuplicatedException
+import com.myapp.service.RelationshipService
+import com.myapp.service.exception.RelationshipNotFoundException
+import org.springframework.http.HttpStatus
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/api/relationships")
+class RelationshipController(
+    private val relationshipService: RelationshipService
+) {
+
+    @PostMapping(value = "/to/{followedId}")
+    fun follow(@PathVariable("followedId") followedId: Long) {
+        relationshipService.follow(followedId)
+    }
+
+    @DeleteMapping(value = "/to/{followedId}")
+    fun unfollow(@PathVariable("followedId") followedId: Long) {
+        relationshipService.unfollow(followedId)
+    }
+
+    @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "already followed")
+    @ExceptionHandler(RelationshipDuplicatedException::class)
+    fun handleRelationshipDuplicated() {
+    }
+
+    @ResponseStatus(value = HttpStatus.NOT_FOUND)
+    @ExceptionHandler(RelationshipNotFoundException::class)
+    fun handleRelationshipNotFound() {
+    }
+
+}
diff --git a/src/main/kotlin/com/myapp/controller/UserController.kt b/src/main/kotlin/com/myapp/controller/UserController.kt
new file mode 100644
index 0000000..0834820
--- /dev/null
+++ b/src/main/kotlin/com/myapp/controller/UserController.kt
@@ -0,0 +1,59 @@
+package com.myapp.controller
+
+import com.myapp.domain.User
+import com.myapp.dto.page.Page
+import com.myapp.dto.request.UserEditParams
+import com.myapp.dto.request.UserNewParams
+import com.myapp.dto.response.ErrorResponse
+import com.myapp.repository.exception.EmailDuplicatedException
+import com.myapp.repository.exception.RecordNotFoundException
+import com.myapp.service.UserService
+import org.springframework.http.HttpStatus
+import org.springframework.web.bind.annotation.*
+import javax.validation.Valid
+
+
+@RestController
+@RequestMapping("/api/users")
+class UserController(
+    private val userService: UserService
+) {
+
+    @GetMapping
+    fun list(
+        @RequestParam(value = "page", required = false) page: Int?,
+        @RequestParam(value = "size", required = false) size: Int?
+    ): Page<User> {
+        return userService.findAll(
+            page = page ?: 1,
+            size = size ?: 5
+        )
+    }
+
+    @PostMapping
+    fun create(@Valid @RequestBody params: UserNewParams): User {
+        return userService.create(params)
+    }
+
+    @GetMapping(path = arrayOf("{id:\\d+}"))
+    fun show(@PathVariable("id") id: Long) = userService.findOne(id)
+
+    @GetMapping(path = arrayOf("/me"))
+    fun showMe() = userService.findMe()
+
+    @PatchMapping(path = arrayOf("/me"))
+    fun updateMe(@Valid @RequestBody params: UserEditParams) = userService.updateMe(params)
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(EmailDuplicatedException::class)
+    fun handleEmailDuplicatedException(e: EmailDuplicatedException): ErrorResponse {
+        return ErrorResponse("email_already_taken", "This email is already taken.")
+    }
+
+    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No user")
+    @ExceptionHandler(RecordNotFoundException::class)
+    fun handleUserNotFound() {
+    }
+
+}
+
diff --git a/src/main/kotlin/com/myapp/controller/UserMicropostController.kt b/src/main/kotlin/com/myapp/controller/UserMicropostController.kt
new file mode 100644
index 0000000..a6912e6
--- /dev/null
+++ b/src/main/kotlin/com/myapp/controller/UserMicropostController.kt
@@ -0,0 +1,35 @@
+package com.myapp.controller
+
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+import com.myapp.service.MicropostService
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/api/users")
+class UserMicropostController(
+    private val micropostService: MicropostService
+) {
+
+    @GetMapping(path = arrayOf("/{userId:\\d+}/microposts"))
+    fun list(
+        @PathVariable("userId") userId: Long,
+        @RequestParam(required = false) sinceId: Long?,
+        @RequestParam(required = false) maxId: Long?,
+        @RequestParam(required = false, defaultValue = "20") count: Int
+    ): List<Micropost> {
+        val pageParams = PageParams(sinceId, maxId, count)
+        return micropostService.findAllByUser(userId, pageParams)
+    }
+
+    @GetMapping(path = arrayOf("/me/microposts"))
+    fun listMyPosts(
+        @RequestParam(required = false) sinceId: Long?,
+        @RequestParam(required = false) maxId: Long?,
+        @RequestParam(required = false, defaultValue = "20") count: Int
+    ): List<Micropost> {
+        val pageParams = PageParams(sinceId, maxId, count)
+        return micropostService.findMyPosts(pageParams)
+    }
+
+}
diff --git a/src/main/kotlin/com/myapp/domain/HasIdentity.kt b/src/main/kotlin/com/myapp/domain/HasIdentity.kt
new file mode 100644
index 0000000..6b4258e
--- /dev/null
+++ b/src/main/kotlin/com/myapp/domain/HasIdentity.kt
@@ -0,0 +1,13 @@
+package com.myapp.domain
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+
+interface HasIdentity<out T> {
+
+    @get:JsonIgnore
+    val _id: T?
+
+    val id: T
+        get() = _id ?: throw RuntimeException("This model is not saved yet.")
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/domain/Micropost.kt b/src/main/kotlin/com/myapp/domain/Micropost.kt
new file mode 100644
index 0000000..60620ec
--- /dev/null
+++ b/src/main/kotlin/com/myapp/domain/Micropost.kt
@@ -0,0 +1,37 @@
+package com.myapp.domain
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.myapp.generated.tables.records.FeedRecord
+import com.myapp.generated.tables.records.MicropostRecord
+import java.util.*
+
+data class Micropost(
+
+    override val _id: Long? = null,
+
+    val content: String,
+
+    val createdAt: Date = Date(),
+
+    val user: User,
+
+    @get:JsonProperty("isMyPost")
+    val isMyPost: Boolean? = null // null means unknown
+
+) : HasIdentity<Long> {
+
+    constructor(record: MicropostRecord, user: User) : this(
+        _id = record.id,
+        content = record.content,
+        createdAt = record.createdAt,
+        user = user
+    )
+
+    constructor(record: FeedRecord, user: User) : this(
+        _id = record.id,
+        content = record.content,
+        createdAt = record.createdAt,
+        user = user
+    )
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/domain/RelatedUser.kt b/src/main/kotlin/com/myapp/domain/RelatedUser.kt
new file mode 100644
index 0000000..49c5276
--- /dev/null
+++ b/src/main/kotlin/com/myapp/domain/RelatedUser.kt
@@ -0,0 +1,29 @@
+package com.myapp.domain
+
+import com.fasterxml.jackson.annotation.JsonGetter
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.myapp.md5
+
+data class RelatedUser(
+
+    override val _id: Long? = null,
+
+    val relationshipId: Long,
+
+    @get:JsonIgnore
+    val username: String,
+
+    val name: String,
+
+    val userStats: UserStats? = null,
+
+    @get:JsonProperty("isFollowedByMe")
+    val isFollowedByMe: Boolean? = null // null means unknown
+
+) : HasIdentity<Long> {
+
+    @JsonGetter
+    fun avatarHash(): String = username.md5()
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/domain/Relationship.kt b/src/main/kotlin/com/myapp/domain/Relationship.kt
new file mode 100644
index 0000000..bf0e9fd
--- /dev/null
+++ b/src/main/kotlin/com/myapp/domain/Relationship.kt
@@ -0,0 +1,7 @@
+package com.myapp.domain
+
+data class Relationship(
+    override val _id: Long? = null,
+    val follower: User,
+    val followed: User
+) : HasIdentity<Long>
diff --git a/src/main/kotlin/com/myapp/domain/User.kt b/src/main/kotlin/com/myapp/domain/User.kt
new file mode 100644
index 0000000..94a33c7
--- /dev/null
+++ b/src/main/kotlin/com/myapp/domain/User.kt
@@ -0,0 +1,55 @@
+package com.myapp.domain
+
+import com.fasterxml.jackson.annotation.JsonGetter
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.myapp.generated.tables.records.UserRecord
+import com.myapp.md5
+import javax.validation.constraints.Pattern
+import javax.validation.constraints.Size
+
+data class User(
+
+    // ------- DB fields -------
+
+    override val _id: Long? = null,
+
+    @get:JsonIgnore
+    @get:Pattern(regexp = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$")
+    @get:Size(min = 4, max = 30)
+    val username: String,
+
+    @get:JsonIgnore
+    val password: String,
+
+    @get:Size(min = 4, max = 30)
+    val name: String,
+
+    // ------- Others -------
+
+    val userStats: UserStats? = null,
+
+    @get:JsonProperty("isFollowedByMe")
+    val isFollowedByMe: Boolean? = null, // null means unknown
+
+    @get:JsonIgnore
+    val isMyself: Boolean? = null // null means unknown
+
+) : HasIdentity<Long> {
+
+    constructor(record: UserRecord) : this(
+        _id = record.id,
+        name = record.name,
+        username = record.username,
+        password = record.password
+    )
+
+    @JsonGetter
+    fun email(): String? {
+        return isMyself?.let { if (it) username else null }
+    }
+
+    @JsonGetter
+    fun avatarHash(): String = username.md5()
+
+}
diff --git a/src/main/kotlin/com/myapp/domain/UserDetailsImpl.kt b/src/main/kotlin/com/myapp/domain/UserDetailsImpl.kt
new file mode 100644
index 0000000..311d268
--- /dev/null
+++ b/src/main/kotlin/com/myapp/domain/UserDetailsImpl.kt
@@ -0,0 +1,16 @@
+package com.myapp.domain
+
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+
+class UserDetailsImpl(val user: User) : UserDetails {
+
+    override fun getUsername() = user.username
+    override fun getPassword() = user.password
+    override fun getAuthorities() = mutableListOf(GrantedAuthority { "ROLE_USER" })
+    override fun isEnabled() = true
+    override fun isCredentialsNonExpired() = true
+    override fun isAccountNonExpired() = true
+    override fun isAccountNonLocked() = true
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/domain/UserStats.kt b/src/main/kotlin/com/myapp/domain/UserStats.kt
new file mode 100644
index 0000000..7fc3983
--- /dev/null
+++ b/src/main/kotlin/com/myapp/domain/UserStats.kt
@@ -0,0 +1,15 @@
+package com.myapp.domain
+
+import com.myapp.generated.tables.records.UserStatsRecord
+
+data class UserStats(
+    val micropostCnt: Long,
+    val followingCnt: Long,
+    val followerCnt: Long
+) {
+    constructor(record: UserStatsRecord) : this(
+        micropostCnt = record.micropostCnt,
+        followerCnt = record.followerCnt,
+        followingCnt = record.followingCnt
+    )
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/dto/page/Page.kt b/src/main/kotlin/com/myapp/dto/page/Page.kt
new file mode 100644
index 0000000..2c1999e
--- /dev/null
+++ b/src/main/kotlin/com/myapp/dto/page/Page.kt
@@ -0,0 +1,6 @@
+package com.myapp.dto.page
+
+interface Page<out T> {
+    val totalPages: Int
+    val content: List<T>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/dto/page/PageImpl.kt b/src/main/kotlin/com/myapp/dto/page/PageImpl.kt
new file mode 100644
index 0000000..d1dca73
--- /dev/null
+++ b/src/main/kotlin/com/myapp/dto/page/PageImpl.kt
@@ -0,0 +1,6 @@
+package com.myapp.dto.page
+
+data class PageImpl<out T>(
+    override val content: List<T>,
+    override val totalPages: Int
+) : Page<T>
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/dto/request/PageParams.kt b/src/main/kotlin/com/myapp/dto/request/PageParams.kt
new file mode 100644
index 0000000..ab2a68c
--- /dev/null
+++ b/src/main/kotlin/com/myapp/dto/request/PageParams.kt
@@ -0,0 +1,7 @@
+package com.myapp.dto.request
+
+data class PageParams(
+    val sinceId: Long? = null,
+    val maxId: Long? = null,
+    val count: Int = 20
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/dto/request/UserEditParams.kt b/src/main/kotlin/com/myapp/dto/request/UserEditParams.kt
new file mode 100644
index 0000000..c0d0d04
--- /dev/null
+++ b/src/main/kotlin/com/myapp/dto/request/UserEditParams.kt
@@ -0,0 +1,13 @@
+package com.myapp.dto.request
+
+import javax.validation.constraints.Size
+
+data class UserEditParams(
+
+    val email: String? = null,
+
+    @get:Size(min = 8, max = 10)
+    val password: String? = null,
+
+    val name: String? = null
+)
diff --git a/src/main/kotlin/com/myapp/dto/request/UserNewParams.kt b/src/main/kotlin/com/myapp/dto/request/UserNewParams.kt
new file mode 100644
index 0000000..9c09723
--- /dev/null
+++ b/src/main/kotlin/com/myapp/dto/request/UserNewParams.kt
@@ -0,0 +1,11 @@
+package com.myapp.dto.request
+
+import javax.validation.constraints.Size
+
+data class UserNewParams(
+    val email: String,
+    @get:Size(min = 8, max = 10)
+    val password: String,
+    val name: String
+)
+
diff --git a/src/main/kotlin/com/myapp/dto/response/ErrorResponse.kt b/src/main/kotlin/com/myapp/dto/response/ErrorResponse.kt
new file mode 100644
index 0000000..6f88012
--- /dev/null
+++ b/src/main/kotlin/com/myapp/dto/response/ErrorResponse.kt
@@ -0,0 +1,6 @@
+package com.myapp.dto.response
+
+data class ErrorResponse(
+    val code: String,
+    val message: String
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/FeedRepository.kt b/src/main/kotlin/com/myapp/repository/FeedRepository.kt
new file mode 100644
index 0000000..cdda1f8
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/FeedRepository.kt
@@ -0,0 +1,8 @@
+package com.myapp.repository
+
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+
+interface FeedRepository {
+    fun findFeed(userId: Long, pageParams: PageParams): List<Micropost>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/FeedRepositoryImpl.kt b/src/main/kotlin/com/myapp/repository/FeedRepositoryImpl.kt
new file mode 100644
index 0000000..ae10193
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/FeedRepositoryImpl.kt
@@ -0,0 +1,36 @@
+package com.myapp.repository
+
+import com.myapp.domain.Micropost
+import com.myapp.domain.User
+import com.myapp.dto.request.PageParams
+import com.myapp.generated.tables.Feed.FEED
+import com.myapp.generated.tables.User.USER
+import org.jooq.DSLContext
+import org.jooq.Record
+import org.springframework.stereotype.Repository
+
+@Repository
+class FeedRepositoryImpl(
+    private val dsl: DSLContext
+) : FeedRepository, Pager {
+
+    override fun findFeed(userId: Long, pageParams: PageParams): List<Micropost> =
+        dsl.select()
+            .from(FEED)
+            .join(USER)
+            .on(FEED.USER_ID.eq(USER.ID))
+            .where(FEED.FEED_USER_ID.eq(userId))
+            .and(pageParams.toCondition(FEED.ID))
+            .orderBy(FEED.ID.desc())
+            .limit(pageParams.count)
+            .fetch(mapper())
+
+    private fun mapper() = { r: Record ->
+        Micropost(
+            record = r.into(FEED),
+            user = User(r.into(USER))
+        )
+    }
+
+}
+
diff --git a/src/main/kotlin/com/myapp/repository/MicropostRepository.kt b/src/main/kotlin/com/myapp/repository/MicropostRepository.kt
new file mode 100644
index 0000000..e9283f4
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/MicropostRepository.kt
@@ -0,0 +1,12 @@
+package com.myapp.repository
+
+import com.myapp.domain.Micropost
+import com.myapp.domain.User
+import com.myapp.dto.request.PageParams
+
+interface MicropostRepository {
+    fun create(micropost: Micropost): Micropost
+    fun delete(id: Long)
+    fun findOne(id: Long): Micropost
+    fun findAllByUser(userId: Long, pageParams: PageParams): List<Micropost>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/MicropostRepositoryImpl.kt b/src/main/kotlin/com/myapp/repository/MicropostRepositoryImpl.kt
new file mode 100644
index 0000000..35fe49e
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/MicropostRepositoryImpl.kt
@@ -0,0 +1,65 @@
+package com.myapp.repository
+
+import com.myapp.domain.Micropost
+import com.myapp.domain.User
+import com.myapp.dto.request.PageParams
+import com.myapp.generated.tables.Micropost.MICROPOST
+import com.myapp.generated.tables.User.USER
+import com.myapp.repository.exception.RecordNotFoundException
+import org.jooq.DSLContext
+import org.jooq.Record
+import org.springframework.stereotype.Repository
+
+@Repository
+class MicropostRepositoryImpl(
+    private val dsl: DSLContext
+) : MicropostRepository, Pager {
+
+    override fun findOne(id: Long) =
+        dsl.select()
+            .from(MICROPOST)
+            .join(USER)
+            .on(MICROPOST.USER_ID.eq(USER.ID))
+            .where(MICROPOST.ID.eq(id))
+            .fetchOne(mapper()) ?: throw RecordNotFoundException()
+
+    override fun findAllByUser(userId: Long, pageParams: PageParams): List<Micropost> {
+        return dsl.select()
+            .from(MICROPOST)
+            .join(USER)
+            .on(MICROPOST.USER_ID.eq(USER.ID))
+            .where(MICROPOST.USER_ID.eq(userId))
+            .and(pageParams.toCondition(MICROPOST.ID))
+            .orderBy(MICROPOST.ID.desc())
+            .limit(pageParams.count)
+            .fetch(mapper())
+    }
+
+    override fun create(micropost: Micropost) =
+        dsl.insertInto(MICROPOST, MICROPOST.CONTENT, MICROPOST.USER_ID)
+            .values(micropost.content, micropost.user.id)
+            .returning()
+            .fetchOne()
+            .let(mapper(micropost))
+
+    override fun delete(id: Long) {
+        dsl.deleteFrom(MICROPOST)
+            .where(MICROPOST.ID.eq(id))
+            .execute()
+    }
+
+    private fun mapper() = { r: Record ->
+        Micropost(
+            record = r.into(MICROPOST),
+            user = User(r.into(USER))
+        )
+    }
+
+    private fun mapper(original: Micropost) = { r: Record ->
+        Micropost(
+            record = r.into(MICROPOST),
+            user = original.user
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/Pager.kt b/src/main/kotlin/com/myapp/repository/Pager.kt
new file mode 100644
index 0000000..d50456d
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/Pager.kt
@@ -0,0 +1,17 @@
+package com.myapp.repository
+
+import com.myapp.dto.request.PageParams
+import org.jooq.Condition
+import org.jooq.Record
+import org.jooq.TableField
+import org.jooq.impl.DSL
+
+interface Pager {
+
+    fun <R : Record> PageParams.toCondition(tableField: TableField<R, Long>): Condition {
+        return DSL.trueCondition()
+            .let { if (sinceId == null) it else it.and(tableField.gt(sinceId)) }
+            .let { if (maxId == null) it else it.and(tableField.lt(maxId)) }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/RelatedUserRepository.kt b/src/main/kotlin/com/myapp/repository/RelatedUserRepository.kt
new file mode 100644
index 0000000..23b9df0
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/RelatedUserRepository.kt
@@ -0,0 +1,9 @@
+package com.myapp.repository
+
+import com.myapp.domain.RelatedUser
+import com.myapp.dto.request.PageParams
+
+interface RelatedUserRepository {
+    fun findFollowers(userId: Long, pageParams: PageParams): List<RelatedUser>
+    fun findFollowings(userId: Long, pageParams: PageParams): List<RelatedUser>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/RelatedUserRepositoryImpl.kt b/src/main/kotlin/com/myapp/repository/RelatedUserRepositoryImpl.kt
new file mode 100644
index 0000000..1693df3
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/RelatedUserRepositoryImpl.kt
@@ -0,0 +1,60 @@
+package com.myapp.repository
+
+import com.myapp.domain.RelatedUser
+import com.myapp.domain.UserStats
+import com.myapp.dto.request.PageParams
+import com.myapp.generated.tables.Relationship.RELATIONSHIP
+import com.myapp.generated.tables.User.USER
+import com.myapp.generated.tables.UserStats.USER_STATS
+import org.jooq.DSLContext
+import org.jooq.Record
+import org.springframework.stereotype.Repository
+import com.myapp.generated.tables.User as UserTable
+
+@Repository
+class RelatedUserRepositoryImpl(
+    private val dsl: DSLContext
+) : RelatedUserRepository, Pager {
+
+    override fun findFollowers(userId: Long, pageParams: PageParams): List<RelatedUser> {
+        return dsl.select()
+            .from(USER)
+            .join(RELATIONSHIP)
+            .on(USER.ID.eq(RELATIONSHIP.FOLLOWER_ID))
+            .join(USER_STATS)
+            .on(USER.ID.eq(USER_STATS.USER_ID))
+            .where(RELATIONSHIP.FOLLOWED_ID.eq(userId))
+            .and(pageParams.toCondition(RELATIONSHIP.ID))
+            .orderBy(RELATIONSHIP.ID.desc())
+            .limit(pageParams.count)
+            .fetch(mapper())
+    }
+
+    override fun findFollowings(userId: Long, pageParams: PageParams): List<RelatedUser> {
+        return dsl.select()
+            .from(USER)
+            .join(RELATIONSHIP)
+            .on(USER.ID.eq(RELATIONSHIP.FOLLOWED_ID))
+            .join(USER_STATS)
+            .on(USER.ID.eq(USER_STATS.USER_ID))
+            .where(RELATIONSHIP.FOLLOWER_ID.eq(userId))
+            .and(pageParams.toCondition(RELATIONSHIP.ID))
+            .orderBy(RELATIONSHIP.ID.desc())
+            .limit(pageParams.count)
+            .fetch(mapper())
+    }
+
+    private fun mapper() = { r: Record ->
+        val userRecord = r.into(USER)
+        val relationshipRecord = r.into(RELATIONSHIP)
+        val userStatsRecord = r.into(USER_STATS)
+        RelatedUser(
+            _id = userRecord.id,
+            username = userRecord.username,
+            name = userRecord.name,
+            relationshipId = relationshipRecord.id,
+            userStats = UserStats(userStatsRecord)
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/RelationshipRepository.kt b/src/main/kotlin/com/myapp/repository/RelationshipRepository.kt
new file mode 100644
index 0000000..21b9395
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/RelationshipRepository.kt
@@ -0,0 +1,12 @@
+package com.myapp.repository
+
+import com.myapp.domain.Relationship
+
+interface RelationshipRepository {
+
+    fun findOneByFollowerAndFollowed(followerId: Long, followedId: Long): Relationship?
+    fun findAllByFollowerAndFollowedUsers(followerId: Long, userIds: List<Long>): List<Relationship>
+    fun create(relationship: Relationship): Relationship
+    fun delete(id: Long)
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/RelationshipRepositoryImpl.kt b/src/main/kotlin/com/myapp/repository/RelationshipRepositoryImpl.kt
new file mode 100644
index 0000000..f01afc4
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/RelationshipRepositoryImpl.kt
@@ -0,0 +1,81 @@
+package com.myapp.repository
+
+import com.myapp.domain.Relationship
+import com.myapp.domain.User
+import com.myapp.generated.tables.Relationship.RELATIONSHIP
+import com.myapp.generated.tables.User.USER
+import com.myapp.repository.exception.RelationshipDuplicatedException
+import org.jooq.DSLContext
+import org.jooq.Record
+import org.springframework.dao.DataIntegrityViolationException
+import org.springframework.stereotype.Repository
+import com.myapp.generated.tables.User as UserTable
+
+@Repository
+class RelationshipRepositoryImpl(
+    private val dsl: DSLContext
+) : RelationshipRepository {
+
+    private val FOLLOWER: UserTable = USER.`as`("follower")
+    private val FOLLOWED: UserTable = USER.`as`("followed")
+
+    override fun findOneByFollowerAndFollowed(followerId: Long, followedId: Long): Relationship? {
+        return dsl.select()
+            .from(RELATIONSHIP)
+            .join(FOLLOWER)
+            .on(RELATIONSHIP.FOLLOWER_ID.eq(FOLLOWER.ID))
+            .join(FOLLOWED)
+            .on(RELATIONSHIP.FOLLOWED_ID.eq(FOLLOWED.ID))
+            .where(RELATIONSHIP.FOLLOWER_ID.eq(followerId))
+            .and(RELATIONSHIP.FOLLOWED_ID.eq(followedId))
+            .fetchOne(mapper())
+    }
+
+    override fun findAllByFollowerAndFollowedUsers(followerId: Long, userIds: List<Long>): List<Relationship> {
+        return dsl.select()
+            .from(RELATIONSHIP)
+            .join(FOLLOWER)
+            .on(RELATIONSHIP.FOLLOWER_ID.eq(FOLLOWER.ID))
+            .join(FOLLOWED)
+            .on(RELATIONSHIP.FOLLOWED_ID.eq(FOLLOWED.ID))
+            .where(RELATIONSHIP.FOLLOWER_ID.eq(followerId))
+            .and(RELATIONSHIP.FOLLOWED_ID.`in`(userIds))
+            .orderBy(RELATIONSHIP.ID.asc())
+            .fetch(mapper())
+    }
+
+    override fun create(relationship: Relationship): Relationship {
+        try {
+            return dsl.insertInto(RELATIONSHIP, RELATIONSHIP.FOLLOWED_ID, RELATIONSHIP.FOLLOWER_ID)
+                .values(relationship.followed.id, relationship.follower.id)
+                .returning()
+                .fetchOne()
+                .let(mapper(relationship))
+        } catch(e: DataIntegrityViolationException) {
+            throw RelationshipDuplicatedException("")
+        }
+    }
+
+    override fun delete(id: Long) {
+        dsl.deleteFrom(RELATIONSHIP)
+            .where(RELATIONSHIP.ID.eq(id))
+            .execute()
+    }
+
+    private fun mapper() = { r: Record ->
+        Relationship(
+            _id = r.into(RELATIONSHIP).id,
+            followed = User(r.into(FOLLOWED)),
+            follower = User(r.into(FOLLOWER))
+        )
+    }
+
+    private fun mapper(original: Relationship) = { r: Record ->
+        Relationship(
+            _id = r.into(RELATIONSHIP).id,
+            followed = original.followed,
+            follower = original.follower
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/UserRepository.kt b/src/main/kotlin/com/myapp/repository/UserRepository.kt
new file mode 100644
index 0000000..4e6d728
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/UserRepository.kt
@@ -0,0 +1,13 @@
+package com.myapp.repository
+
+import com.myapp.domain.User
+import com.myapp.dto.page.Page
+
+interface UserRepository {
+    fun findOne(id: Long): User
+    fun findOneWithStats(id: Long): User
+    fun findOneByUsername(username: String): User?
+    fun findAll(page: Int, size: Int): Page<User>
+    fun create(user: User): User
+    fun update(user: User)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/UserRepositoryImpl.kt b/src/main/kotlin/com/myapp/repository/UserRepositoryImpl.kt
new file mode 100644
index 0000000..c3529a5
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/UserRepositoryImpl.kt
@@ -0,0 +1,111 @@
+package com.myapp.repository
+
+import com.myapp.domain.User
+import com.myapp.domain.UserStats
+import com.myapp.dto.page.Page
+import com.myapp.dto.page.PageImpl
+import com.myapp.generated.tables.User.USER
+import com.myapp.generated.tables.UserStats.USER_STATS
+import com.myapp.generated.tables.records.UserRecord
+import com.myapp.repository.exception.EmailDuplicatedException
+import com.myapp.repository.exception.RecordInvalidException
+import com.myapp.repository.exception.RecordNotFoundException
+import org.jooq.DSLContext
+import org.jooq.Record
+import org.slf4j.LoggerFactory
+import org.springframework.dao.DataIntegrityViolationException
+import org.springframework.stereotype.Repository
+import javax.validation.Validator
+
+@Repository
+class UserRepositoryImpl(
+    private val dsl: DSLContext,
+    private val validator: Validator
+) : UserRepository {
+
+    @Suppress("unused")
+    private val logger = LoggerFactory.getLogger(UserRepositoryImpl::class.java)
+
+    override fun findOne(id: Long) =
+        dsl.selectFrom(USER)
+            .where(USER.ID.eq(id))
+            .fetchOne(mapper()) ?: throw RecordNotFoundException()
+
+    override fun findOneWithStats(id: Long) =
+        dsl.select()
+            .from(USER)
+            .join(USER_STATS)
+            .on(USER.ID.eq(USER_STATS.USER_ID))
+            .where(USER.ID.eq(id))
+            .fetchOne(mapperWithStats()) ?: throw RecordNotFoundException()
+
+    override fun findOneByUsername(username: String): User? {
+        return dsl.selectFrom(USER)
+            .where(USER.USERNAME.eq(username))
+            .fetchOne(mapper())
+    }
+
+    override fun findAll(page: Int, size: Int): Page<User> {
+        val content = dsl.selectFrom(USER)
+            .orderBy(USER.ID)
+            .seek((page - 1) * size.toLong())
+            .limit(size)
+            .fetch(mapper())
+
+        val totalPages: Int = dsl.selectCount()
+            .from(USER)
+            .fetchOne()
+            .value1()
+            .div(size.toDouble())
+            .ceil()
+            .toInt()
+
+        return PageImpl(
+            content = content,
+            totalPages = totalPages
+        )
+    }
+
+    override fun create(user: User): User {
+        validate(user)
+
+        try {
+            return dsl.insertInto(USER, USER.USERNAME, USER.PASSWORD, USER.NAME)
+                .values(user.username, user.password, user.name)
+                .returning()
+                .fetchOne()
+                .let(mapper())
+        } catch (e: DataIntegrityViolationException) {
+            throw EmailDuplicatedException("")
+        }
+    }
+
+    override fun update(user: User) {
+        validate(user)
+
+        try {
+            dsl.update(USER)
+                .set(USER.USERNAME, user.username)
+                .set(USER.PASSWORD, user.password)
+                .set(USER.NAME, user.name)
+                .where(USER.ID.eq(user.id))
+                .execute()
+        } catch(e: DataIntegrityViolationException) {
+            throw EmailDuplicatedException("")
+        }
+    }
+
+    private fun validate(user: User) = validator.validate(user).apply {
+        if (isNotEmpty()) throw RecordInvalidException(toString())
+    }
+
+    private fun mapper(): (UserRecord) -> User = ::User
+
+    private fun mapperWithStats(): (Record) -> User = {
+        mapper().invoke(it.into(USER)).copy(
+            userStats = UserStats(it.into(USER_STATS))
+        )
+    }
+
+    private fun Double.ceil() = Math.ceil(this)
+}
diff --git a/src/main/kotlin/com/myapp/repository/exception/EmailDuplicatedException.kt b/src/main/kotlin/com/myapp/repository/exception/EmailDuplicatedException.kt
new file mode 100644
index 0000000..b99f281
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/exception/EmailDuplicatedException.kt
@@ -0,0 +1,5 @@
+package com.myapp.repository.exception
+
+import org.springframework.dao.DuplicateKeyException
+
+class EmailDuplicatedException(msg: String) : DuplicateKeyException(msg)
diff --git a/src/main/kotlin/com/myapp/repository/exception/RecordInvalidException.kt b/src/main/kotlin/com/myapp/repository/exception/RecordInvalidException.kt
new file mode 100644
index 0000000..a68358c
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/exception/RecordInvalidException.kt
@@ -0,0 +1,5 @@
+package com.myapp.repository.exception
+
+import org.springframework.dao.DataIntegrityViolationException
+
+class RecordInvalidException(msg: String) : DataIntegrityViolationException(msg)
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/exception/RecordNotFoundException.kt b/src/main/kotlin/com/myapp/repository/exception/RecordNotFoundException.kt
new file mode 100644
index 0000000..dcf0340
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/exception/RecordNotFoundException.kt
@@ -0,0 +1,3 @@
+package com.myapp.repository.exception
+
+class RecordNotFoundException : RuntimeException()
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/repository/exception/RelationshipDuplicatedException.kt b/src/main/kotlin/com/myapp/repository/exception/RelationshipDuplicatedException.kt
new file mode 100644
index 0000000..eaeea41
--- /dev/null
+++ b/src/main/kotlin/com/myapp/repository/exception/RelationshipDuplicatedException.kt
@@ -0,0 +1,5 @@
+package com.myapp.repository.exception
+
+import org.springframework.dao.DuplicateKeyException
+
+class RelationshipDuplicatedException(msg: String) : DuplicateKeyException(msg)
diff --git a/src/main/kotlin/com/myapp/service/FeedService.kt b/src/main/kotlin/com/myapp/service/FeedService.kt
new file mode 100644
index 0000000..d558438
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/FeedService.kt
@@ -0,0 +1,10 @@
+package com.myapp.service
+
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+
+interface FeedService {
+
+    fun findFeed(pageParams: PageParams): List<Micropost>
+
+}
diff --git a/src/main/kotlin/com/myapp/service/FeedServiceImpl.kt b/src/main/kotlin/com/myapp/service/FeedServiceImpl.kt
new file mode 100644
index 0000000..3b07b88
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/FeedServiceImpl.kt
@@ -0,0 +1,24 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+import com.myapp.repository.FeedRepository
+import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
+
+@Service
+@Transactional
+class FeedServiceImpl(
+    private val feedRepository: FeedRepository,
+    override val securityContextService: SecurityContextService
+) : FeedService, WithCurrentUser {
+
+    override fun findFeed(pageParams: PageParams): List<Micropost> {
+        val currentUser = currentUserOrThrow()
+
+        return feedRepository.findFeed(currentUser.id, pageParams)
+            .map { it.copy(isMyPost = it.user.id == currentUser.id) }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/MicropostService.kt b/src/main/kotlin/com/myapp/service/MicropostService.kt
new file mode 100644
index 0000000..4467ee1
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/MicropostService.kt
@@ -0,0 +1,12 @@
+package com.myapp.service
+
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+
+
+interface MicropostService {
+    fun create(content: String): Micropost
+    fun delete(id: Long)
+    fun findAllByUser(userId: Long, pageParams: PageParams): List<Micropost>
+    fun findMyPosts(pageParams: PageParams): List<Micropost>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/MicropostServiceImpl.kt b/src/main/kotlin/com/myapp/service/MicropostServiceImpl.kt
new file mode 100644
index 0000000..be7b7a4
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/MicropostServiceImpl.kt
@@ -0,0 +1,48 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+import com.myapp.repository.MicropostRepository
+import com.myapp.service.exception.NotAuthorizedException
+import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
+
+
+@Service
+@Transactional
+class MicropostServiceImpl(
+    private val micropostRepository: MicropostRepository,
+    override val securityContextService: SecurityContextService
+) : MicropostService, WithCurrentUser {
+
+    override fun findAllByUser(userId: Long, pageParams: PageParams): List<Micropost> {
+        val currentUser = currentUser()
+        val isMyPost = currentUser?.let { it.id == userId }
+
+        return micropostRepository.findAllByUser(userId, pageParams)
+            .map { it.copy(isMyPost = isMyPost) }
+    }
+
+    override fun findMyPosts(pageParams: PageParams): List<Micropost> {
+        val currentUser = currentUserOrThrow()
+        return findAllByUser(currentUser.id, pageParams)
+    }
+
+    override fun create(content: String) =
+        micropostRepository.create(Micropost(
+            content = content,
+            user = currentUserOrThrow()
+        ))
+
+    override fun delete(id: Long) {
+        val currentUser = currentUserOrThrow()
+        val post = micropostRepository.findOne(id)
+
+        if (post.user.id == currentUser.id)
+            micropostRepository.delete(id)
+        else
+            throw NotAuthorizedException("You can not delete this post.")
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/RelatedUserService.kt b/src/main/kotlin/com/myapp/service/RelatedUserService.kt
new file mode 100644
index 0000000..e2491e0
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/RelatedUserService.kt
@@ -0,0 +1,9 @@
+package com.myapp.service
+
+import com.myapp.domain.RelatedUser
+import com.myapp.dto.request.PageParams
+
+interface RelatedUserService {
+    fun findFollowings(userId: Long, pageParams: PageParams): List<RelatedUser>
+    fun findFollowers(userId: Long, pageParams: PageParams): List<RelatedUser>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/RelatedUserServiceImpl.kt b/src/main/kotlin/com/myapp/service/RelatedUserServiceImpl.kt
new file mode 100644
index 0000000..5fc0706
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/RelatedUserServiceImpl.kt
@@ -0,0 +1,48 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.RelatedUser
+import com.myapp.dto.request.PageParams
+import com.myapp.repository.RelatedUserRepository
+import com.myapp.repository.RelationshipRepository
+import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
+
+
+@Service
+@Transactional
+class RelatedUserServiceImpl(
+    private val relatedUserRepository: RelatedUserRepository,
+    private val relationshipRepository: RelationshipRepository,
+    override val securityContextService: SecurityContextService
+) : RelatedUserService, WithCurrentUser {
+
+    override fun findFollowers(userId: Long, pageParams: PageParams): List<RelatedUser> {
+        val relatedUsers = relatedUserRepository.findFollowers(userId, pageParams)
+        val myRelationships = currentUser()?.let {
+            val relatedUserIds = relatedUsers.map { it.id }.distinct()
+            relationshipRepository.findAllByFollowerAndFollowedUsers(it.id, relatedUserIds)
+        }
+
+        return relatedUsers.map { relatedUser ->
+            relatedUser.copy(
+                isFollowedByMe = myRelationships?.any { it.followed.id == relatedUser.id }
+            )
+        }
+    }
+
+    override fun findFollowings(userId: Long, pageParams: PageParams): List<RelatedUser> {
+        val relatedUsers = relatedUserRepository.findFollowings(userId, pageParams)
+        val myRelationships = currentUser()?.let {
+            val relatedUserIds = relatedUsers.map { it.id }.distinct()
+            relationshipRepository.findAllByFollowerAndFollowedUsers(it.id, relatedUserIds)
+        }
+
+        return relatedUsers.map { relatedUser ->
+            relatedUser.copy(
+                isFollowedByMe = myRelationships?.any { it.followed.id == relatedUser.id }
+            )
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/RelationshipService.kt b/src/main/kotlin/com/myapp/service/RelationshipService.kt
new file mode 100644
index 0000000..2d797c3
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/RelationshipService.kt
@@ -0,0 +1,6 @@
+package com.myapp.service
+
+interface RelationshipService {
+    fun follow(userId: Long)
+    fun unfollow(userId: Long)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/RelationshipServiceImpl.kt b/src/main/kotlin/com/myapp/service/RelationshipServiceImpl.kt
new file mode 100644
index 0000000..86ad057
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/RelationshipServiceImpl.kt
@@ -0,0 +1,44 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.Relationship
+import com.myapp.repository.RelationshipRepository
+import com.myapp.repository.UserRepository
+import com.myapp.service.exception.RelationshipNotFoundException
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
+
+
+@Service
+@Transactional
+class RelationshipServiceImpl(
+    private val userRepository: UserRepository,
+    private val relationshipRepository: RelationshipRepository,
+    override val securityContextService: SecurityContextService
+) : RelationshipService, WithCurrentUser {
+
+    @Suppress("unused")
+    private val logger = LoggerFactory.getLogger(RelationshipServiceImpl::class.java)
+
+    override fun follow(userId: Long) {
+        val user = userRepository.findOne(userId)
+        val currentUser = currentUserOrThrow()
+        val relationship = Relationship(
+            follower = currentUser,
+            followed = user
+        )
+        relationshipRepository.create(relationship)
+    }
+
+    override fun unfollow(userId: Long) {
+        val currentUser = currentUserOrThrow()
+        val relationship = relationshipRepository
+            .findOneByFollowerAndFollowed(currentUser.id, userId)
+
+        relationship?.let {
+            relationshipRepository.delete(it.id)
+        } ?: throw RelationshipNotFoundException()
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/UserDetailsServiceImpl.kt b/src/main/kotlin/com/myapp/service/UserDetailsServiceImpl.kt
new file mode 100644
index 0000000..3710428
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/UserDetailsServiceImpl.kt
@@ -0,0 +1,25 @@
+package com.myapp.service
+
+import com.myapp.domain.UserDetailsImpl
+import com.myapp.repository.UserRepository
+import org.springframework.security.authentication.AccountStatusUserDetailsChecker
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+import org.springframework.stereotype.Service
+
+
+@Service
+class UserDetailsServiceImpl(
+    private val userRepository: UserRepository
+) : UserDetailsService {
+
+    override fun loadUserByUsername(username: String): UserDetailsImpl =
+        userRepository
+            .findOneByUsername(username)
+            ?.let(::UserDetailsImpl)
+            ?.apply {
+                AccountStatusUserDetailsChecker().check(this)
+            } ?: throw UsernameNotFoundException("user not found.")
+
+}
+
diff --git a/src/main/kotlin/com/myapp/service/UserService.kt b/src/main/kotlin/com/myapp/service/UserService.kt
new file mode 100644
index 0000000..924158c
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/UserService.kt
@@ -0,0 +1,14 @@
+package com.myapp.service
+
+import com.myapp.domain.User
+import com.myapp.dto.page.Page
+import com.myapp.dto.request.UserEditParams
+import com.myapp.dto.request.UserNewParams
+
+interface UserService {
+    fun findAll(page: Int, size: Int = 20): Page<User>
+    fun findOne(id: Long): User
+    fun findMe(): User
+    fun create(params: UserNewParams): User
+    fun updateMe(params: UserEditParams)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/UserServiceImpl.kt b/src/main/kotlin/com/myapp/service/UserServiceImpl.kt
new file mode 100644
index 0000000..ba2725f
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/UserServiceImpl.kt
@@ -0,0 +1,62 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.User
+import com.myapp.dto.page.Page
+import com.myapp.dto.request.UserEditParams
+import com.myapp.dto.request.UserNewParams
+import com.myapp.repository.RelationshipRepository
+import com.myapp.repository.UserRepository
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
+
+@Service
+@Transactional
+class UserServiceImpl(
+    private val userRepository: UserRepository,
+    private val relationshipRepository: RelationshipRepository,
+    override val securityContextService: SecurityContextService
+) : UserService, WithCurrentUser {
+
+    override fun findOne(id: Long): User {
+        val currentUser = currentUser()
+        val user = userRepository.findOneWithStats(id)
+        val isFollowedByMe = currentUser?.let {
+            relationshipRepository.findOneByFollowerAndFollowed(it.id, user.id) != null
+        }
+        val isMyself = currentUser?.let { it.id == user.id }
+
+        return user.copy(
+            isFollowedByMe = isFollowedByMe,
+            isMyself = isMyself
+        )
+    }
+
+    override fun findMe(): User {
+        val currentUser = currentUserOrThrow()
+        return findOne(currentUser.id)
+    }
+
+    override fun findAll(page: Int, size: Int): Page<User> =
+        userRepository.findAll(page, size)
+
+    override fun create(params: UserNewParams): User {
+        return userRepository.create(User(
+            username = params.email,
+            password = encrypt(params.password),
+            name = params.name
+        ))
+    }
+
+    override fun updateMe(params: UserEditParams) {
+        val currentUser = currentUserOrThrow()
+        userRepository.update(currentUser.copy(
+            username = params.email ?: currentUser.username,
+            password = params.password?.let { encrypt(it) } ?: currentUser.password,
+            name = params.name ?: currentUser.name
+        ))
+    }
+
+    private fun encrypt(secret: String) = BCryptPasswordEncoder().encode(secret)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/WithCurrentUser.kt b/src/main/kotlin/com/myapp/service/WithCurrentUser.kt
new file mode 100644
index 0000000..d699f6b
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/WithCurrentUser.kt
@@ -0,0 +1,16 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.User
+import org.springframework.security.access.AccessDeniedException
+
+interface WithCurrentUser {
+
+    val securityContextService: SecurityContextService
+
+    fun currentUser(): User? =
+        securityContextService.currentUser()
+
+    fun currentUserOrThrow(): User =
+        securityContextService.currentUser() ?: throw AccessDeniedException("")
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/exception/NotAuthorizedException.kt b/src/main/kotlin/com/myapp/service/exception/NotAuthorizedException.kt
new file mode 100644
index 0000000..948901d
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/exception/NotAuthorizedException.kt
@@ -0,0 +1,3 @@
+package com.myapp.service.exception
+
+class NotAuthorizedException(msg: String) : RuntimeException(msg)
\ No newline at end of file
diff --git a/src/main/kotlin/com/myapp/service/exception/RelationshipNotFoundException.kt b/src/main/kotlin/com/myapp/service/exception/RelationshipNotFoundException.kt
new file mode 100644
index 0000000..564e642
--- /dev/null
+++ b/src/main/kotlin/com/myapp/service/exception/RelationshipNotFoundException.kt
@@ -0,0 +1,3 @@
+package com.myapp.service.exception
+
+class RelationshipNotFoundException : RuntimeException()
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 1b7bedd..051579e 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -3,73 +3,51 @@ spring:
     banner-mode: 'OFF'
   profiles:
     active: dev
+  jooq:
+    sql-dialect: mysql
 security:
   basic:
     enabled: false
-endpoints:
-  enabled: false
-  health.enabled: true
 management:
   context-path: /manage
+
 app.jwt.secret: qwerty
 ---
 spring:
   profiles: dev
   datasource:
-    driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
-    url: jdbc:log4jdbc:h2:./db/dev;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE
+    driverClassName: org.h2.Driver
+    url: jdbc:h2:./db/dev;MODE=MySQL
     username: sa
     password:
-  jpa:
-    database: H2
-    hibernate:
-      ddl-auto: update
-  thymeleaf:
-    cache: false
-flyway:
-  enabled: false
 ---
 spring:
   profiles: dev2
   datasource:
+    type: com.zaxxer.hikari.HikariDataSource
     driverClassName: org.mariadb.jdbc.Driver
-    jdbcUrl: jdbc:mysql://localhost:3306/springboot-angular2-tutorial?useSSL=false
+    url: jdbc:mysql://localhost:3306/springboot-angular2-tutorial
     username: root
     password:
-  jpa:
-    database: MYSQL
-    hibernate:
-      ddl-auto: validate
 logging:
   config: classpath:logback-prod.xml
 ---
 spring:
   profiles: test
   datasource:
-    driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
-    url: jdbc:log4jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE
+    driverClassName: org.h2.Driver
+    url: jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE
     username: sa
     password:
-  jpa:
-    database: H2
-    hibernate:
-      ddl-auto: create
-flyway:
-  enabled: false
-server:
-  port: 0
 ---
 spring:
   profiles: stg
   datasource:
+    type: com.zaxxer.hikari.HikariDataSource
     driverClassName: org.mariadb.jdbc.Driver
-    jdbcUrl: jdbc:mysql://${MYSQL_ENDPOINT}/ebdb?useSSL=false
+    url: jdbc:mysql://${MYSQL_ENDPOINT}/ebdb?useSSL=false
     username: micropostuser
     password: ENC(jSxIbUwZQ5KgQJkOLGbYsZV83hH3oI0D)
-  jpa:
-    database: MYSQL
-    hibernate:
-      ddl-auto: validate
 logging:
   config: classpath:logback-prod.xml
 app.jwt.secret: ENC(L4Y4hVbkFgZC3VFVWH28jTiCSSCBAx6xeM/nBSIahjrbc/JggbhTiKr2w9RLu9sx)
@@ -77,14 +55,12 @@ app.jwt.secret: ENC(L4Y4hVbkFgZC3VFVWH28jTiCSSCBAx6xeM/nBSIahjrbc/JggbhTiKr2w9RL
 spring:
   profiles: prod
   datasource:
+    type: com.zaxxer.hikari.HikariDataSource
     driverClassName: org.mariadb.jdbc.Driver
-    jdbcUrl: jdbc:mysql://${MYSQL_ENDPOINT}/ebdb?useSSL=false
+    url: jdbc:mysql://${MYSQL_ENDPOINT}/ebdb?useSSL=false
     username: micropostuser
     password: ENC(eKw2P1jyrqKKs6KVg9Ql9iaJC8ve2kD8)
-  jpa:
-    database: MYSQL
-    hibernate:
-      ddl-auto: validate
 logging:
   config: classpath:logback-prod.xml
 app.jwt.secret: ENC(t0NojUi8WhsSCti12Qj8CgzzrBA1Wt0PC9R1N5KvRltEcegGhxSy1zEmI7OxqzOA)
+
diff --git a/src/main/resources/db/migration/V2__create_feed_view.sql b/src/main/resources/db/migration/V2__create_feed_view.sql
new file mode 100644
index 0000000..232bcc3
--- /dev/null
+++ b/src/main/resources/db/migration/V2__create_feed_view.sql
@@ -0,0 +1,14 @@
+CREATE VIEW `feed` AS
+  SELECT
+    m.*,
+    r.follower_id AS feed_user_id
+  FROM micropost m
+    INNER JOIN relationship r
+      ON m.user_id = r.followed_id
+  UNION
+  SELECT
+    m.*,
+    u.id AS feed_user_id
+  FROM micropost m
+    INNER JOIN user u
+      ON m.user_id = u.id;
diff --git a/src/main/resources/db/migration/V3__create_user_stats_view.sql b/src/main/resources/db/migration/V3__create_user_stats_view.sql
new file mode 100644
index 0000000..ccc1402
--- /dev/null
+++ b/src/main/resources/db/migration/V3__create_user_stats_view.sql
@@ -0,0 +1,14 @@
+CREATE VIEW `user_stats` AS
+  SELECT
+    u.id                         AS user_id,
+    (SELECT count(*)
+     FROM relationship r
+     WHERE r.followed_id = u.id) AS follower_cnt,
+    (SELECT count(*)
+     FROM relationship r
+     WHERE r.follower_id = u.id) AS following_cnt,
+    (SELECT count(*)
+     FROM micropost m
+     WHERE m.user_id = u.id)     AS micropost_cnt
+  FROM user u
+
diff --git a/src/main/resources/db/migration/V4__set_micropost_default_created_at.sql b/src/main/resources/db/migration/V4__set_micropost_default_created_at.sql
new file mode 100644
index 0000000..af1ee25
--- /dev/null
+++ b/src/main/resources/db/migration/V4__set_micropost_default_created_at.sql
@@ -0,0 +1,2 @@
+ALTER TABLE micropost
+  CHANGE created_at created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;
diff --git a/src/main/resources/log4jdbc.log4j2.properties b/src/main/resources/log4jdbc.log4j2.properties
deleted file mode 100644
index 37e0d34..0000000
--- a/src/main/resources/log4jdbc.log4j2.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-log4jdbc.dump.sql.maxlinelength=0
-log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index 1c9e046..95ed62a 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <configuration>
     <include resource="org/springframework/boot/logging/logback/base.xml"/>
-    <logger name="jdbc" level="OFF"/>
-    <logger name="jdbc.sqltiming" level="DEBUG"/>
-    <logger name="jdbc.resultsettable" level="DEBUG"/>
     <logger name="com.myapp" level="DEBUG"/>
-</configuration>
+    <logger name="org.jooq" level="DEBUG"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/groovy/com/myapp/auth/TestSecurityConfig.groovy b/src/test/groovy/com/myapp/auth/TestSecurityConfig.groovy
deleted file mode 100644
index 155d0a2..0000000
--- a/src/test/groovy/com/myapp/auth/TestSecurityConfig.groovy
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.myapp.auth
-
-import com.myapp.repository.UserRepository
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import org.springframework.security.core.userdetails.UserDetailsService
-import spock.mock.DetachedMockFactory
-
-@SuppressWarnings("GroovyUnusedDeclaration")
-@TestConfiguration
-class TestSecurityConfig {
-
-    private DetachedMockFactory factory = new DetachedMockFactory()
-
-    @Bean
-    UserDetailsService userDetailsService() {
-        factory.Stub(UserDetailsService)
-    }
-
-    @Bean
-    TokenAuthenticationService tokenAuthenticationService() {
-        factory.Stub(TokenAuthenticationService)
-    }
-
-    @Bean
-    UserRepository userRepository() {
-        factory.Stub(UserRepository)
-    }
-}
diff --git a/src/test/groovy/com/myapp/controller/AuthControllerTest.groovy b/src/test/groovy/com/myapp/controller/AuthControllerTest.groovy
deleted file mode 100644
index 11e4e16..0000000
--- a/src/test/groovy/com/myapp/controller/AuthControllerTest.groovy
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.auth.TokenHandler
-import com.myapp.auth.UserAuthentication
-import com.myapp.domain.User
-import com.myapp.service.SecurityContextService
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import org.springframework.http.MediaType
-import org.springframework.security.authentication.AuthenticationManager
-import org.springframework.security.authentication.BadCredentialsException
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
-import org.springframework.security.core.Authentication
-import spock.mock.DetachedMockFactory
-
-import static groovy.json.JsonOutput.toJson
-import static org.hamcrest.Matchers.is
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-
-@WebMvcTest(AuthController)
-class AuthControllerTest extends BaseControllerTest {
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        AuthenticationManager authenticationManager(DetachedMockFactory f) {
-            return f.Mock(AuthenticationManager)
-        }
-
-        @Bean
-        TokenHandler tokenHandler(DetachedMockFactory f) {
-            return f.Mock(TokenHandler)
-        }
-
-        @Bean
-        SecurityContextService securityContextService(DetachedMockFactory f) {
-            return f.Mock(SecurityContextService)
-        }
-    }
-
-    @Autowired
-    AuthenticationManager authenticationManager
-
-    @Autowired
-    TokenHandler tokenHandler
-
-    @Autowired
-    SecurityContextService securityContextService
-
-    def setup() {
-        User user = new User(id: 1, username: "test1@test.com", password: "secret", name: "test1")
-        UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken("test1@test.com", "secret");
-        authenticationManager.authenticate(loginToken) >> new UserAuthentication(user)
-        authenticationManager.authenticate(_ as Authentication) >> {
-            throw new BadCredentialsException("")
-        }
-        securityContextService.currentUser() >> Optional.of(user)
-        tokenHandler.createTokenForUser(user) >> "created jwt"
-    }
-
-    def "can auth when username and password are correct"() {
-        when:
-        def response = perform(post("/api/auth")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(email: "test1@test.com", password: "secret"))
-        )
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$.token', is("created jwt")))
-        }
-    }
-
-    def "can not auth when username or password is not correct"() {
-        when:
-        def response = perform(post("/api/auth")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(email: "test2@test.com", password: "secret"))
-        )
-
-        then:
-        with(response) {
-            andExpect(status().isUnauthorized())
-        }
-    }
-
-}
-
diff --git a/src/test/groovy/com/myapp/controller/BaseControllerTest.groovy b/src/test/groovy/com/myapp/controller/BaseControllerTest.groovy
deleted file mode 100644
index b2d756d..0000000
--- a/src/test/groovy/com/myapp/controller/BaseControllerTest.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.auth.TokenAuthenticationService
-import com.myapp.auth.UserAuthentication
-import com.myapp.domain.User
-import com.myapp.service.RelationshipService
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import org.springframework.context.annotation.ComponentScan
-import org.springframework.security.core.Authentication
-import org.springframework.test.context.ActiveProfiles
-import org.springframework.test.web.servlet.MockMvc
-import org.springframework.test.web.servlet.RequestBuilder
-import org.springframework.test.web.servlet.ResultActions
-import spock.lang.Specification
-import spock.mock.DetachedMockFactory
-
-import javax.servlet.http.HttpServletRequest
-
-@ActiveProfiles("test")
-@ComponentScan(basePackages = ["com.myapp.auth"])
-abstract class BaseControllerTest extends Specification {
-
-    @SuppressWarnings("GroovyUnusedDeclaration")
-    final Logger logger = LoggerFactory.getLogger(this.getClass());
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        DetachedMockFactory detachedMockFactory() {
-            return new DetachedMockFactory()
-        }
-    }
-
-    @Autowired
-    private MockMvc mockMvc
-
-    @Autowired
-    private TokenAuthenticationService tokenAuthenticationService
-
-    ResultActions perform(RequestBuilder requestBuilder) {
-        return mockMvc.perform(requestBuilder)
-    }
-
-    User signIn(User user) {
-        Authentication auth = new UserAuthentication(user)
-        tokenAuthenticationService.getAuthentication(_ as HttpServletRequest) >> auth
-        return user
-    }
-
-    User signIn() {
-        User user = new User(id: 1, username: "test@test.com", password: "secret", name: "test")
-        Authentication auth = new UserAuthentication(user)
-        tokenAuthenticationService.getAuthentication(_ as HttpServletRequest) >> auth
-        return user
-    }
-}
diff --git a/src/test/groovy/com/myapp/controller/FeedControllerTest.groovy b/src/test/groovy/com/myapp/controller/FeedControllerTest.groovy
deleted file mode 100644
index 9ba4c1f..0000000
--- a/src/test/groovy/com/myapp/controller/FeedControllerTest.groovy
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.domain.Micropost
-import com.myapp.domain.User
-import com.myapp.dto.PageParams
-import com.myapp.dto.PostDTO
-import com.myapp.service.MicropostService
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import spock.mock.DetachedMockFactory
-
-import java.time.LocalDateTime
-
-import static org.hamcrest.Matchers.*
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-
-@WebMvcTest(FeedController)
-class FeedControllerTest extends BaseControllerTest {
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        MicropostService micropostService(DetachedMockFactory f) {
-            f.Stub(MicropostService, name: "micropostService")
-        }
-    }
-
-    @Autowired
-    MicropostService micropostService
-
-    def "can show feed when signed in"() {
-        given:
-        User user = signIn()
-        Date now = new Date()
-
-        def feed = [PostDTO.newInstance(new Micropost(id: 1, user: user, content: "content1", createdAt: now), true)]
-        micropostService.findAsFeed(_ as PageParams) >> feed
-
-        when:
-        def response = perform(get("/api/feed"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$', hasSize(1)))
-            andExpect(jsonPath('$[0].content', is("content1")))
-            andExpect(jsonPath('$[0].isMyPost', is(true)))
-            andExpect(jsonPath('$[0].createdAt', greaterThanOrEqualTo(now.time)))
-            andExpect(jsonPath('$[0].user.email', nullValue()))
-            andExpect(jsonPath('$[0].user.avatarHash', is("b642b4217b34b1e8d3bd915fc65c4452")))
-        }
-    }
-
-    def "can not show feed when not signed in"() {
-        when:
-        def response = perform(get("/api/feed"))
-
-        then:
-        response.andExpect(status().isUnauthorized())
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/controller/MicropostControllerTest.groovy b/src/test/groovy/com/myapp/controller/MicropostControllerTest.groovy
deleted file mode 100644
index cc42e06..0000000
--- a/src/test/groovy/com/myapp/controller/MicropostControllerTest.groovy
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.domain.Micropost
-import com.myapp.service.MicropostService
-import com.myapp.service.exceptions.NotPermittedException
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import org.springframework.http.MediaType
-import spock.mock.DetachedMockFactory
-
-import static groovy.json.JsonOutput.toJson
-import static org.hamcrest.Matchers.is
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-
-@WebMvcTest(MicropostController)
-class MicropostControllerTest extends BaseControllerTest {
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        MicropostService micropostService(DetachedMockFactory f) {
-            f.Mock(MicropostService, name: "micropostService")
-        }
-    }
-
-    @Autowired
-    MicropostService micropostService
-
-    def "can create a micropost"() {
-        given:
-        signIn()
-        String content = "my content"
-
-        when:
-        def response = perform(post("/api/microposts")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(content: content))
-        )
-
-        then:
-        micropostService.saveMyPost(_ as Micropost) >> new Micropost(content)
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$.content').exists())
-            andExpect(jsonPath('$.content', is(content)))
-        }
-    }
-
-    def "can not create a micropost when not signed in"() {
-        when:
-        def response = perform(post("/api/microposts")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(content: "test"))
-        )
-
-        then:
-        response.andExpect(status().isUnauthorized())
-    }
-
-    def "can delete a micropost"() {
-        given:
-        signIn()
-
-        when:
-        def response = perform(delete("/api/microposts/1"))
-
-        then:
-        1 * micropostService.delete(1)
-        response.andExpect(status().isOk())
-    }
-
-    def "can not delete a micropost when not signed in"() {
-        when:
-        def response = perform(delete("/api/microposts/1"))
-
-        then:
-        response.andExpect(status().isUnauthorized())
-    }
-
-    def "can not delete a micropost when have no permission"() {
-        given:
-        signIn()
-        micropostService.delete(1) >> { throw new NotPermittedException("") }
-
-        when:
-        def response = perform(delete("/api/microposts/1"))
-
-        then:
-        response.andExpect(status().isForbidden())
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/controller/RelationshipControllerTest.groovy b/src/test/groovy/com/myapp/controller/RelationshipControllerTest.groovy
deleted file mode 100644
index abcfb71..0000000
--- a/src/test/groovy/com/myapp/controller/RelationshipControllerTest.groovy
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.service.RelationshipService
-import com.myapp.service.exceptions.RelationshipNotFoundException
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import spock.mock.DetachedMockFactory
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-
-@WebMvcTest(RelationshipController)
-class RelationshipControllerTest extends BaseControllerTest {
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        RelationshipService relationshipService(DetachedMockFactory f) {
-            return f.Mock(RelationshipService)
-        }
-    }
-
-    @Autowired
-    RelationshipService relationshipService
-
-    def "can follow another user"() {
-        given:
-        signIn()
-
-        when:
-        def response = perform(post("/api/relationships/to/1"))
-
-        then:
-        1 * relationshipService.follow(1)
-        response.andExpect(status().isOk())
-    }
-
-    def "can not follow another user when not signed in"() {
-        when:
-        def response = perform(post("/api/relationships/to/1"))
-
-        then:
-        response.andExpect(status().isUnauthorized())
-    }
-
-    def "can unfollow another user"() {
-        given:
-        signIn()
-
-        when:
-        def response = perform(delete("/api/relationships/to/1"))
-
-        then:
-        1 * relationshipService.unfollow(1)
-        response.andExpect(status().isOk())
-    }
-
-    def "can not unfollow another user when not signed in"() {
-        when:
-        def response = perform(delete("/api/relationships/to/1"))
-
-        then:
-        response.andExpect(status().isUnauthorized())
-    }
-
-    def "can not unfollow another user when have already followed"() {
-        given:
-        signIn()
-        relationshipService.unfollow(1) >> {
-            throw new RelationshipNotFoundException()
-        }
-
-        when:
-        def response = perform(delete("/api/relationships/to/1"))
-
-        then:
-        response.andExpect(status().isNotFound())
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/controller/UserControllerTest.groovy b/src/test/groovy/com/myapp/controller/UserControllerTest.groovy
deleted file mode 100644
index 26d2cea..0000000
--- a/src/test/groovy/com/myapp/controller/UserControllerTest.groovy
+++ /dev/null
@@ -1,259 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.Utils
-import com.myapp.domain.User
-import com.myapp.domain.UserStats
-import com.myapp.dto.UserDTO
-import com.myapp.dto.UserParams
-import com.myapp.service.UserService
-import com.myapp.service.exceptions.UserNotFoundException
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import org.springframework.dao.DataIntegrityViolationException
-import org.springframework.data.domain.PageImpl
-import org.springframework.data.domain.PageRequest
-import org.springframework.http.MediaType
-import spock.mock.DetachedMockFactory
-
-import static groovy.json.JsonOutput.toJson
-import static org.hamcrest.Matchers.*
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-
-@WebMvcTest(UserController)
-class UserControllerTest extends BaseControllerTest {
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        UserService userService(DetachedMockFactory f) {
-            return f.Mock(UserService)
-        }
-    }
-
-    @Autowired
-    UserService userService
-
-    def "can signup"() {
-        given:
-        def email = "akirasosa@test.com"
-        def password = "secret123"
-        def name = "akira"
-
-        when:
-        def response = perform(post("/api/users")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(email: email, password: password, name: name))
-        )
-
-        then:
-        1 * userService.create(new UserParams(email, password, name))
-        response.andExpect(status().isOk())
-    }
-
-    def "can not signup when email is duplicated"() {
-        given:
-        def email = "akirasosa@test.com"
-        def password = "secret123"
-        def name = "akira1"
-        userService.create(_ as UserParams) >> {
-            throw new DataIntegrityViolationException("")
-        }
-
-        when:
-        def response = perform(post("/api/users")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(email: email, password: password, name: name))
-        )
-
-        then:
-        with(response) {
-            andExpect(status().isBadRequest())
-            andExpect(jsonPath('$.code', is("email_already_taken")))
-        }
-    }
-
-    def "can list users when signed in"() {
-        given:
-        signIn()
-        userService.findAll(_ as PageRequest) >> {
-            List<UserDTO> content = (0..1).collect {
-                User u = new User(id: it, username: "test${it}@test.com", password: "secret", name: "test${it}")
-                UserDTO.builder()
-                        .id(u.id)
-                        .name(u.name)
-                        .avatarHash(Utils.md5(u.username))
-                        .build()
-            }
-            return new PageImpl<>(content)
-        }
-
-        when:
-        def response = perform(get("/api/users"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$.content').exists())
-            andExpect(jsonPath('$.content', hasSize(2)))
-            andExpect(jsonPath('$.content[0].name', is("test0")))
-            andExpect(jsonPath('$.content[0].email', nullValue()))
-            andExpect(jsonPath('$.content[0].avatarHash', is("17c9ea0d5cb514cd00d3a71eb312b9dc")))
-            andExpect(jsonPath('$.content[1].name', is("test1")))
-        }
-    }
-
-    def "can not list users when not signed in"() {
-        given:
-        userService.findAll(_ as PageRequest) >> []
-
-        when:
-        def response = perform(get("/api/users"))
-
-        then:
-        response.andExpect(status().isUnauthorized())
-    }
-
-    def "can show user"() {
-        given:
-        userService.findOne(1) >> {
-            User user = new User(id: 1, username: "test1@test.com", password: "secret", name: "test")
-            UserStats userStats = new UserStats(3, 2, 1)
-            UserDTO userDTO = UserDTO.builder()
-                    .id(user.id)
-                    .name(user.name)
-                    .avatarHash(Utils.md5(user.username))
-                    .userStats(userStats)
-                    .build()
-            return Optional.of(userDTO)
-        }
-
-        when:
-        def response = perform(get("/api/users/1"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$.name', is("test")))
-            andExpect(jsonPath('$.email', nullValue()))
-            andExpect(jsonPath('$.avatarHash', is("94fba03762323f286d7c3ca9e001c541")))
-            andExpect(jsonPath('$.isFollowedByMe', nullValue()))
-            andExpect(jsonPath('$.userStats').exists())
-            andExpect(jsonPath('$.userStats.micropostCnt', is(3)))
-            andExpect(jsonPath('$.userStats.followingCnt', is(2)))
-            andExpect(jsonPath('$.userStats.followerCnt', is(1)))
-        }
-    }
-
-    def "can not show user when user was not found"() {
-        given:
-        userService.findOne(1) >> { throw new UserNotFoundException() }
-
-        when:
-        def response = perform(get("/api/users/1"))
-
-        then:
-        with(response) {
-            andExpect(status().isNotFound())
-        }
-    }
-
-    def "can show logged in user when signed in"() {
-        given:
-        User user = signIn()
-        userService.findMe() >> {
-            UserStats userStats = new UserStats(3, 2, 1)
-            UserDTO userDTO = UserDTO.builder()
-                    .id(user.id)
-                    .name(user.name)
-                    .email(user.username)
-                    .avatarHash(Utils.md5(user.username))
-                    .userStats(userStats)
-                    .isFollowedByMe(false)
-                    .build()
-            Optional.of(userDTO)
-        }
-
-        when:
-        def response = perform(get("/api/users/me"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$.name', is("test")))
-            andExpect(jsonPath('$.email', is("test@test.com")))
-            andExpect(jsonPath('$.avatarHash', is("b642b4217b34b1e8d3bd915fc65c4452")))
-            andExpect(jsonPath('$.isFollowedByMe', is(false)))
-            andExpect(jsonPath('$.userStats').exists())
-            andExpect(jsonPath('$.userStats.micropostCnt', is(3)))
-            andExpect(jsonPath('$.userStats.followingCnt', is(2)))
-            andExpect(jsonPath('$.userStats.followerCnt', is(1)))
-        }
-    }
-
-    def "can not show logged in user when not signed in"() {
-        when:
-        def response = perform(get("/api/users/me"))
-
-        then:
-        with(response) {
-            andExpect(status().isUnauthorized())
-        }
-    }
-
-    def "can update me"() {
-        given:
-        signIn()
-        String email = "test2@test.com"
-        String password = "very secret"
-        String name = "new name"
-        userService.updateMe(new UserParams(email, password, name)) >> {
-            return new User(id: 1, username: email, password: password, name: name)
-        }
-
-        when:
-        def response = perform(patch("/api/users/me")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(email: email, password: password, name: name))
-        )
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-        }
-    }
-
-    def "can not update me when not signed in"() {
-        when:
-        def response = perform(patch("/api/users/me")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(email: "test", password: "test", name: "test"))
-        )
-
-        then:
-        with(response) {
-            andExpect(status().isUnauthorized())
-        }
-    }
-
-    def "can not update me when parameter is invalid"() {
-        given:
-        signIn()
-
-        when:
-        // password is too short
-        def response = perform(patch("/api/users/me")
-                .contentType(MediaType.APPLICATION_JSON)
-                .content(toJson(email: "test@test.com", password: "a", name: "test"))
-        )
-
-        then:
-        with(response) {
-            andExpect(status().isBadRequest())
-        }
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/controller/UserMicropostControllerTest.groovy b/src/test/groovy/com/myapp/controller/UserMicropostControllerTest.groovy
deleted file mode 100644
index a584622..0000000
--- a/src/test/groovy/com/myapp/controller/UserMicropostControllerTest.groovy
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.domain.Micropost
-import com.myapp.domain.User
-import com.myapp.dto.PageParams
-import com.myapp.dto.PostDTO
-import com.myapp.service.MicropostService
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import spock.mock.DetachedMockFactory
-
-import static org.hamcrest.Matchers.*
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-
-@WebMvcTest(UserMicropostController)
-class UserMicropostControllerTest extends BaseControllerTest {
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        MicropostService micropostService(DetachedMockFactory f) {
-            return f.Mock(MicropostService)
-        }
-    }
-
-    @Autowired
-    MicropostService micropostService
-
-    def "can list microposts"() {
-        given:
-        User user = new User(id: 1, username: "akira@test.com", password: "secret", name: "akira")
-        micropostService.findByUser(1, new PageParams()) >> (0..1).collect {
-            Micropost post = new Micropost(id: it, content: "content${it}", user: user, createdAt: new Date())
-            return PostDTO.newInstance(post, null)
-        }
-
-
-        when:
-        def response = perform(get("/api/users/${user.id}/microposts/"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$', hasSize(2)))
-            andExpect(jsonPath('$[0].content', is("content0")))
-            andExpect(jsonPath('$[0].isMyPost', nullValue()))
-            andExpect(jsonPath('$[0].user.name', is("akira")))
-            andExpect(jsonPath('$[1].content', is("content1")))
-        }
-    }
-
-    def "can list my microposts when signed in"() {
-        given:
-        User user = signIn()
-        micropostService.findMyPosts(new PageParams()) >> (0..1).collect {
-            Micropost post = new Micropost(id: it, content: "content${it}", user: user, createdAt: new Date())
-            return PostDTO.newInstance(post, true)
-        }
-
-
-        when:
-        def response = perform(get("/api/users/me/microposts"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$', hasSize(2)))
-            andExpect(jsonPath('$[0].content', is("content0")))
-            andExpect(jsonPath('$[0].isMyPost', is(true)))
-            andExpect(jsonPath('$[0].user.name', is(user.name)))
-            andExpect(jsonPath('$[1].content', is("content1")))
-        }
-    }
-
-    def "can not list my microposts when signed in"() {
-        given:
-        micropostService.findMyPosts(new PageParams()) >> []
-
-        when:
-        def response = perform(get("/api/users/me/microposts"))
-
-        then:
-        with(response) {
-            andExpect(status().isUnauthorized())
-        }
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/controller/UserRelationshipControllerTest.groovy b/src/test/groovy/com/myapp/controller/UserRelationshipControllerTest.groovy
deleted file mode 100644
index bfae4ae..0000000
--- a/src/test/groovy/com/myapp/controller/UserRelationshipControllerTest.groovy
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.myapp.controller
-
-import com.myapp.Utils
-import com.myapp.domain.Relationship
-import com.myapp.domain.User
-import com.myapp.domain.UserStats
-import com.myapp.dto.PageParams
-import com.myapp.dto.RelatedUserDTO
-import com.myapp.service.RelationshipService
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.boot.test.context.TestConfiguration
-import org.springframework.context.annotation.Bean
-import spock.mock.DetachedMockFactory
-
-import static org.hamcrest.Matchers.is
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-
-@WebMvcTest(UserRelationshipController)
-class UserRelationshipControllerTest extends BaseControllerTest {
-
-    @TestConfiguration
-    static class Config {
-        @Bean
-        RelationshipService relationshipService(DetachedMockFactory f) {
-            return f.Mock(RelationshipService)
-        }
-    }
-
-    @Autowired
-    RelationshipService relationshipService
-
-    def "can list followings"() {
-        given:
-        User follower = new User(id: 1, username: "akira@test.com", password: "secret", name: "akira")
-        User followed1 = new User(id: 2, username: "test1@test.com", password: "secret", name: "test1")
-        User followed2 = new User(id: 3, username: "test2@test.com", password: "secret", name: "test2")
-        Relationship r1 = new Relationship(id: 1, follower: follower, followed: followed1)
-        Relationship r2 = new Relationship(id: 2, follower: follower, followed: followed2)
-        relationshipService.findFollowings(follower.id, new PageParams()) >> [
-                buildRelatedUserDTO(followed1, r1),
-                buildRelatedUserDTO(followed2, r2),
-        ]
-
-        when:
-        def response = perform(get("/api/users/${follower.id}/followings"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$[0].name', is("test1")))
-            andExpect(jsonPath('$[0].avatarHash', is("94fba03762323f286d7c3ca9e001c541")))
-            andExpect(jsonPath('$[0].relationshipId', is(r1.id.intValue())))
-            andExpect(jsonPath('$[1].name', is("test2")))
-        }
-    }
-
-    def "can list followers"() {
-        given:
-        User followed = new User(id: 1, username: "akira@test.com", password: "secret", name: "akira")
-        User follower1 = new User(id: 2, username: "test1@test.com", password: "secret", name: "test1")
-        User follower2 = new User(id: 3, username: "test2@test.com", password: "secret", name: "test2")
-        Relationship r1 = new Relationship(id: 1, follower: follower1, followed: followed)
-        Relationship r2 = new Relationship(id: 2, follower: follower2, followed: followed)
-        relationshipService.findFollowers(followed.id, new PageParams()) >> [
-                buildRelatedUserDTO(follower1, r1),
-                buildRelatedUserDTO(follower2, r2),
-        ]
-
-        when:
-        def response = perform(get("/api/users/${followed.id}/followers"))
-
-        then:
-        with(response) {
-            andExpect(status().isOk())
-            andExpect(jsonPath('$[0].name', is("test1")))
-            andExpect(jsonPath('$[0].avatarHash', is("94fba03762323f286d7c3ca9e001c541")))
-            andExpect(jsonPath('$[0].relationshipId', is(r1.id.intValue())))
-            andExpect(jsonPath('$[1].name', is("test2")))
-        }
-    }
-
-    private static buildRelatedUserDTO(User u, Relationship r) {
-        UserStats us = new UserStats(1, 2, 3)
-        return RelatedUserDTO.builder()
-                .id(u.id)
-                .avatarHash(Utils.md5(u.username))
-                .name(u.name)
-                .userStats(us)
-                .relationshipId(r.id)
-                .build()
-    }
-}
diff --git a/src/test/groovy/com/myapp/repository/BaseRepositoryTest.groovy b/src/test/groovy/com/myapp/repository/BaseRepositoryTest.groovy
deleted file mode 100644
index 4cb37b8..0000000
--- a/src/test/groovy/com/myapp/repository/BaseRepositoryTest.groovy
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.myapp.repository
-
-import com.myapp.config.QueryDSLConfig
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
-import org.springframework.context.annotation.Import
-import org.springframework.test.context.ActiveProfiles
-import spock.lang.Specification
-
-@ActiveProfiles("test")
-@Import(value = [QueryDSLConfig])
-@DataJpaTest
-abstract class BaseRepositoryTest extends Specification {
-}
diff --git a/src/test/groovy/com/myapp/repository/MicropostCustomRepositoryTest.groovy b/src/test/groovy/com/myapp/repository/MicropostCustomRepositoryTest.groovy
deleted file mode 100644
index 6a0b8e3..0000000
--- a/src/test/groovy/com/myapp/repository/MicropostCustomRepositoryTest.groovy
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.myapp.repository
-
-import com.myapp.domain.Micropost
-import com.myapp.domain.Relationship
-import com.myapp.domain.User
-import com.myapp.dto.PageParams
-import org.springframework.beans.factory.annotation.Autowired
-
-class MicropostCustomRepositoryTest extends BaseRepositoryTest {
-
-    @Autowired
-    MicropostRepository micropostRepository
-
-    @Autowired
-    MicropostCustomRepository micropostCustomRepository
-
-    @Autowired
-    UserRepository userRepository
-
-    @Autowired
-    RelationshipRepository relationshipRepository
-
-    def "can find feed"() {
-        given:
-        User follower = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        User followed = userRepository.save(new User(username: "test1@test.com", password: "secret", name: "test1"))
-        User another = userRepository.save(new User(username: "test2@test.com", password: "secret", name: "test2"))
-        relationshipRepository.save(new Relationship(follower: follower, followed: followed))
-        [follower, followed, another].each { u ->
-            micropostRepository.save(new Micropost(content: "test1", user: u))
-            micropostRepository.save(new Micropost(content: "test2", user: u))
-        }
-
-        when:
-        List<MicropostCustomRepository.Row> result = micropostCustomRepository.findAsFeed(follower, new PageParams()).collect()
-
-        then:
-        result.size() == 4
-        result.first().micropost.user.id == followed.id
-        result.last().micropost.user.id == follower.id
-    }
-
-    def "can find feed by since_id or max_id"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        Micropost post1 = micropostRepository.save(new Micropost(content: "test1", user: user))
-        Micropost post2 = micropostRepository.save(new Micropost(content: "test2", user: user))
-        Micropost post3 = micropostRepository.save(new Micropost(content: "test3", user: user))
-
-        when:
-        List<MicropostCustomRepository.Row> result = micropostCustomRepository.findAsFeed(user, new PageParams(sinceId: post2.id)).collect()
-
-        then:
-        result.size() == 1
-        result.first().micropost == post3
-
-        when:
-        result = micropostCustomRepository.findAsFeed(user, new PageParams(maxId: post2.id)).collect()
-
-        then:
-        result.size() == 1
-        result.first().micropost == post1
-    }
-
-    def "can find posts by user"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        Micropost post1 = micropostRepository.save(new Micropost(content: "test1", user: user))
-        Micropost post2 = micropostRepository.save(new Micropost(content: "test2", user: user))
-        Micropost post3 = micropostRepository.save(new Micropost(content: "test3", user: user))
-
-        when:
-        List<MicropostCustomRepository.Row> result = micropostCustomRepository.findByUser(user, new PageParams(sinceId: post2.id)).collect()
-
-        then:
-        result.size() == 1
-        result.first().micropost == post3
-
-        when:
-        result = micropostCustomRepository.findByUser(user, new PageParams(maxId: post2.id)).collect()
-
-        then:
-        result.size() == 1
-        result.first().micropost == post1
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/repository/RelatedUserCustomRepositoryTest.groovy b/src/test/groovy/com/myapp/repository/RelatedUserCustomRepositoryTest.groovy
deleted file mode 100644
index 95588f8..0000000
--- a/src/test/groovy/com/myapp/repository/RelatedUserCustomRepositoryTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.myapp.repository
-
-import com.myapp.domain.Relationship
-import com.myapp.domain.User
-import com.myapp.dto.PageParams
-import org.springframework.beans.factory.annotation.Autowired
-
-class RelatedUserCustomRepositoryTest extends BaseRepositoryTest {
-
-    @Autowired
-    UserRepository userRepository
-
-    @Autowired
-    RelatedUserCustomRepository relatedUserCustomRepository
-
-    @Autowired
-    RelationshipRepository relationshipRepository
-
-    def "findFollowings"() {
-        given:
-        User follower = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        User followed1 = userRepository.save(new User(username: "test1@test.com", password: "secret", name: "test1"))
-        User followed2 = userRepository.save(new User(username: "test2@test.com", password: "secret", name: "test2"))
-        Relationship r1 = relationshipRepository.save(new Relationship(follower: follower, followed: followed1))
-        Relationship r2 = relationshipRepository.save(new Relationship(follower: follower, followed: followed2))
-
-        when:
-        List<RelatedUserCustomRepository.Row> result = relatedUserCustomRepository.findFollowings(follower, new PageParams())
-
-        then:
-        result[0].user.username == "test2@test.com"
-        result[0].relationship.id == r2.id
-        result[1].user.username == "test1@test.com"
-        result[1].relationship.id == r1.id
-    }
-
-    def "findFollowers"() {
-        given:
-        User followed = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        User follower1 = userRepository.save(new User(username: "test1@test.com", password: "secret", name: "test1"))
-        User follower2 = userRepository.save(new User(username: "test2@test.com", password: "secret", name: "test2"))
-        Relationship r1 = relationshipRepository.save(new Relationship(followed: followed, follower: follower1))
-        Relationship r2 = relationshipRepository.save(new Relationship(followed: followed, follower: follower2))
-
-        when:
-        List<RelatedUserCustomRepository.Row> result = relatedUserCustomRepository.findFollowers(followed, new PageParams())
-
-        then:
-        result[0].user.username == "test2@test.com"
-        result[0].relationship.id == r2.id
-        result[1].user.username == "test1@test.com"
-        result[1].relationship.id == r1.id
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/repository/RelationshipRepositoryTest.groovy b/src/test/groovy/com/myapp/repository/RelationshipRepositoryTest.groovy
deleted file mode 100644
index ec49ab1..0000000
--- a/src/test/groovy/com/myapp/repository/RelationshipRepositoryTest.groovy
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.myapp.repository
-
-import com.myapp.domain.Relationship
-import com.myapp.domain.User
-import org.springframework.beans.factory.annotation.Autowired
-
-class RelationshipRepositoryTest extends BaseRepositoryTest {
-
-    @Autowired
-    RelationshipRepository relationshipRepository
-
-    @Autowired
-    UserRepository userRepository
-
-    def "can find one by follower and followed"() {
-        given:
-        User follower = new User(username: "akira@test.com", password: "secret", name: "akira")
-        User followed = new User(username: "satoru@test.com", password: "secret", name: "satoru")
-        userRepository.save([follower, followed])
-        Relationship relationship = new Relationship(follower: follower, followed: followed)
-
-        when:
-        def result = relationshipRepository.findOneByFollowerAndFollowed(follower, followed)
-
-        then:
-        !result.isPresent()
-
-        when:
-        relationshipRepository.save(relationship)
-        result = relationshipRepository.findOneByFollowerAndFollowed(follower, followed)
-
-        then:
-        result.get() == relationship
-    }
-
-    def "can find all by follower and followed in"() {
-        given:
-        User follower = new User(username: "akira@test.com", password: "secret", name: "akira")
-        User followed1 = new User(username: "followed1@test.com", password: "secret", name: "satoru")
-        User followed2 = new User(username: "followed2@test.com", password: "secret", name: "satoru")
-        userRepository.save([follower, followed1, followed2])
-        Relationship relationship1 = new Relationship(follower: follower, followed: followed1)
-        Relationship relationship2 = new Relationship(follower: follower, followed: followed2)
-        relationshipRepository.save([relationship1, relationship2])
-
-        when:
-        List<Relationship> result = relationshipRepository.findAllByFollowerAndFollowedIn(follower, [followed1]).collect()
-
-        then:
-        result.size() == 1
-        result.first().followed == followed1
-    }
-}
diff --git a/src/test/groovy/com/myapp/repository/RepositoryTestConfig.groovy b/src/test/groovy/com/myapp/repository/RepositoryTestConfig.groovy
deleted file mode 100644
index 7dec3b3..0000000
--- a/src/test/groovy/com/myapp/repository/RepositoryTestConfig.groovy
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.myapp.repository
-
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration
-import org.springframework.boot.autoconfigure.domain.EntityScan
-import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
-import org.springframework.context.annotation.Configuration
-import org.springframework.data.jpa.repository.config.EnableJpaRepositories
-
-@Configuration
-@EnableJpaRepositories(basePackages = "com.myapp.repository")
-@EntityScan("com.myapp.domain")
-@EnableAutoConfiguration(exclude = [FlywayAutoConfiguration])
-class RepositoryTestConfig {
-}
diff --git a/src/test/groovy/com/myapp/repository/UserCustomRepositoryTest.groovy b/src/test/groovy/com/myapp/repository/UserCustomRepositoryTest.groovy
deleted file mode 100644
index 0e67de8..0000000
--- a/src/test/groovy/com/myapp/repository/UserCustomRepositoryTest.groovy
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.myapp.repository
-
-import com.myapp.domain.User
-import org.springframework.beans.factory.annotation.Autowired
-
-class UserCustomRepositoryTest extends BaseRepositoryTest {
-
-    @Autowired
-    UserRepository userRepository
-
-    @Autowired
-    UserCustomRepository userCustomRepository
-
-    def "findOne"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-
-        when:
-        UserCustomRepository.Row result = userCustomRepository.findOne(user.id).get()
-
-        then:
-        result.user == user
-        result.userStats.followerCnt == 0
-        result.userStats.followingCnt == 0
-        result.userStats.micropostCnt == 0
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/service/BaseServiceTest.groovy b/src/test/groovy/com/myapp/service/BaseServiceTest.groovy
deleted file mode 100644
index 0def6da..0000000
--- a/src/test/groovy/com/myapp/service/BaseServiceTest.groovy
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.myapp.service
-
-import com.myapp.domain.User
-import com.myapp.repository.BaseRepositoryTest
-
-abstract class BaseServiceTest extends BaseRepositoryTest {
-
-    SecurityContextService securityContextService = Mock(SecurityContextService)
-
-    Optional<User> currentUser
-
-    def setup() {
-        // default not signed in
-        currentUser = Optional.empty()
-        securityContextService.currentUser() >> { currentUser }
-    }
-
-    def signIn(User user) {
-        currentUser = Optional.of(user)
-    }
-
-    def cleanup() {
-        currentUser = Optional.empty()
-    }
-
-
-}
diff --git a/src/test/groovy/com/myapp/service/MicropostServiceTest.groovy b/src/test/groovy/com/myapp/service/MicropostServiceTest.groovy
deleted file mode 100644
index 81b8003..0000000
--- a/src/test/groovy/com/myapp/service/MicropostServiceTest.groovy
+++ /dev/null
@@ -1,149 +0,0 @@
-package com.myapp.service
-
-import com.myapp.domain.Micropost
-import com.myapp.domain.Relationship
-import com.myapp.domain.User
-import com.myapp.dto.PageParams
-import com.myapp.dto.PostDTO
-import com.myapp.repository.MicropostCustomRepository
-import com.myapp.repository.MicropostRepository
-import com.myapp.repository.RelationshipRepository
-import com.myapp.repository.UserRepository
-import com.myapp.service.exceptions.NotPermittedException
-import org.springframework.beans.factory.annotation.Autowired
-import spock.lang.Shared
-
-@SuppressWarnings("GroovyPointlessBoolean")
-class MicropostServiceTest extends BaseServiceTest {
-
-    @Autowired
-    MicropostRepository micropostRepository
-
-    @Autowired
-    MicropostCustomRepository micropostCustomRepository
-
-    @Autowired
-    UserRepository userRepository
-
-    @Shared
-    MicropostService micropostService
-
-    @Autowired
-    RelationshipRepository relationshipRepository
-
-    def setup() {
-        micropostService = new MicropostServiceImpl(micropostRepository, userRepository, micropostCustomRepository, relationshipRepository, securityContextService)
-    }
-
-    def "can delete micropost when have a permission"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        Micropost post = micropostRepository.save(new Micropost(user: user, content: "test"))
-        signIn(user)
-
-        when:
-        micropostService.delete(post.id)
-
-        then:
-        micropostRepository.count() == 0
-    }
-
-    def "can not delete micropost when have no permission"() {
-        given:
-        User currentUser = userRepository.save(new User(username: "test1@test.com", password: "secret", name: "akira"))
-        User anotherUser = userRepository.save(new User(username: "test2@test.com", password: "secret", name: "akira"))
-        Micropost post = micropostRepository.save(new Micropost(user: anotherUser, content: "test"))
-        signIn(currentUser)
-
-        when:
-        micropostService.delete(post.id)
-
-        then:
-        thrown(NotPermittedException)
-        micropostRepository.count() == 1
-    }
-
-    def "can find posts as feed"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        micropostRepository.save(new Micropost(user: user, content: "my content"))
-        signIn(user)
-
-        User followed = userRepository.save(new User(username: "test1@test.com", password: "secret", name: "test1"))
-        relationshipRepository.save(new Relationship(follower: user, followed: followed))
-        micropostRepository.save(new Micropost(user: followed, content: "follower content"))
-
-        when:
-        List<PostDTO> posts = micropostService.findAsFeed(new PageParams())
-
-        then:
-        posts.size() == 2
-        posts.first().content == 'follower content'
-        posts.first().isMyPost == false
-        posts.last().isMyPost == true
-    }
-
-    def "can find posts by user when not signed in"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        micropostRepository.save(new Micropost(user: user, content: "my content"))
-
-        when:
-        List<PostDTO> posts = micropostService.findByUser(user.id, new PageParams())
-
-        then:
-        posts.size() == 1
-        posts.first().content == "my content"
-        posts.first().isMyPost == null
-    }
-
-    def "can find posts by user when signed in"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        signIn(user)
-
-        when:
-        micropostRepository.save(new Micropost(user: user, content: "my content"))
-        List<PostDTO> posts = micropostService.findByUser(user.id, new PageParams())
-
-        then:
-        posts.first().isMyPost == true
-
-        when:
-        User anotherUser = userRepository.save(new User(username: "satoru@test.com", password: "secret", name: "satoru"))
-        micropostRepository.save(new Micropost(user: anotherUser, content: "my content"))
-        List<PostDTO> anotherPosts = micropostService.findByUser(anotherUser.id, new PageParams())
-
-        then:
-        anotherPosts.first().isMyPost == false
-    }
-
-    def "can find my posts"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        signIn(user)
-        micropostRepository.save(new Micropost(user: user, content: "my content"))
-
-        when:
-        List<PostDTO> posts = micropostService.findMyPosts(new PageParams())
-
-        then:
-        posts.size() == 1
-        posts.first().content == "my content"
-        posts.first().isMyPost == true
-    }
-
-    def "can save my post"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        signIn(user)
-        Micropost post = new Micropost("test post")
-
-        when:
-        micropostService.saveMyPost(post)
-
-        then:
-        micropostRepository.findAll().first() == post
-        post.user == user
-    }
-}
diff --git a/src/test/groovy/com/myapp/service/RelationshipServiceTest.groovy b/src/test/groovy/com/myapp/service/RelationshipServiceTest.groovy
deleted file mode 100644
index 1145d8f..0000000
--- a/src/test/groovy/com/myapp/service/RelationshipServiceTest.groovy
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.myapp.service
-
-import com.myapp.domain.Relationship
-import com.myapp.domain.User
-import com.myapp.dto.PageParams
-import com.myapp.repository.RelatedUserCustomRepository
-import com.myapp.repository.RelationshipRepository
-import com.myapp.repository.UserRepository
-import org.springframework.beans.factory.annotation.Autowired
-
-@SuppressWarnings("GroovyPointlessBoolean")
-class RelationshipServiceTest extends BaseServiceTest {
-
-    @Autowired
-    UserRepository userRepository
-
-    @Autowired
-    RelatedUserCustomRepository relatedUserCustomRepository
-
-    @Autowired
-    RelationshipRepository relationshipRepository
-
-    RelationshipService relationshipService
-
-    def setup() {
-        relationshipService = new RelationshipServiceImpl(relationshipRepository, relatedUserCustomRepository, userRepository, securityContextService)
-    }
-
-    def "can find followings when not signed in"() {
-        given:
-        User follower = userRepository.save(new User(username: "follower@test.com", password: "secret", name: "akira"))
-        User followed = userRepository.save(new User(username: "followed@test.com", password: "secret", name: "akira"))
-        relationshipRepository.save(new Relationship(follower: follower, followed: followed))
-
-        when:
-        def followings = relationshipService.findFollowings(follower.id, new PageParams())
-
-        then:
-        followings.first().isFollowedByMe == null
-    }
-
-    def "can find followings when signed in"() {
-        given:
-        User follower = userRepository.save(new User(username: "follower@test.com", password: "secret", name: "akira"))
-        User followed1 = userRepository.save(new User(username: "followed1@test.com", password: "secret", name: "followed1"))
-        User followed2 = userRepository.save(new User(username: "followed2@test.com", password: "secret", name: "followed2"))
-        relationshipRepository.save(new Relationship(follower: follower, followed: followed1))
-        relationshipRepository.save(new Relationship(follower: follower, followed: followed2))
-        signIn(followed1)
-
-        when:
-        def followings = relationshipService.findFollowings(follower.id, new PageParams())
-
-        then:
-        followings.first().name == "followed2"
-        followings.first().isFollowedByMe == false
-        followings.last().name == "followed1"
-        followings.last().isFollowedByMe == false
-    }
-
-    def "can find followers when not signed in"() {
-        given:
-        User followed = userRepository.save(new User(username: "followed@test.com", password: "secret", name: "akira"))
-        User follower = userRepository.save(new User(username: "follower@test.com", password: "secret", name: "akira"))
-        relationshipRepository.save(new Relationship(follower: follower, followed: followed))
-
-        when:
-        def followers = relationshipService.findFollowers(followed.id, new PageParams())
-
-        then:
-        followers.first().isFollowedByMe == null
-    }
-
-    def "can find followers when signed in"() {
-        given:
-        User followed = userRepository.save(new User(username: "followed@test.com", password: "secret", name: "akira"))
-        User follower1 = userRepository.save(new User(username: "follower1@test.com", password: "secret", name: "follower1"))
-        User follower2 = userRepository.save(new User(username: "follower2@test.com", password: "secret", name: "follower2"))
-        relationshipRepository.save(new Relationship(follower: follower1, followed: followed))
-        relationshipRepository.save(new Relationship(follower: follower2, followed: followed))
-        signIn(follower1)
-
-        when:
-        def followers = relationshipService.findFollowers(followed.id, new PageParams())
-
-        then:
-        followers.first().name == "follower2"
-        followers.first().isFollowedByMe == false
-        followers.last().name == "follower1"
-        followers.last().isFollowedByMe == false
-    }
-
-    def "can follow and unfollow"() {
-        given:
-        User currentUser = userRepository.save(new User(username: "test1@test.com", password: "secret", name: "akira"))
-        User targetUser = userRepository.save(new User(username: "test2@test.com", password: "secret", name: "akira"))
-        signIn(currentUser)
-
-        when:
-        relationshipService.follow(targetUser.id)
-
-        then:
-        def relationShip = relationshipRepository.findAll().first()
-        relationShip.follower == currentUser
-        relationShip.followed == targetUser
-
-        when:
-        relationshipService.unfollow(targetUser.id)
-
-        then:
-        relationshipRepository.count() == 0
-    }
-
-}
diff --git a/src/test/groovy/com/myapp/service/UserServiceTest.groovy b/src/test/groovy/com/myapp/service/UserServiceTest.groovy
deleted file mode 100644
index c2e8c2b..0000000
--- a/src/test/groovy/com/myapp/service/UserServiceTest.groovy
+++ /dev/null
@@ -1,152 +0,0 @@
-package com.myapp.service
-
-import com.myapp.domain.Relationship
-import com.myapp.domain.User
-import com.myapp.dto.UserDTO
-import com.myapp.dto.UserParams
-import com.myapp.repository.RelationshipRepository
-import com.myapp.repository.UserCustomRepository
-import com.myapp.repository.UserRepository
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.data.domain.Page
-import org.springframework.data.domain.PageRequest
-import org.springframework.security.core.userdetails.UserDetails
-import org.springframework.security.core.userdetails.UsernameNotFoundException
-
-@SuppressWarnings("GroovyPointlessBoolean")
-class UserServiceTest extends BaseServiceTest {
-
-    @Autowired
-    UserRepository userRepository
-
-    @Autowired
-    UserCustomRepository userCustomRepository
-
-    @Autowired
-    RelationshipRepository relationshipRepository
-
-    UserService userService
-
-    def setup() {
-        userService = new UserServiceImpl(userRepository, userCustomRepository, relationshipRepository, securityContextService)
-    }
-
-    def "can find a user when not signed in"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-
-        when:
-        UserDTO userDTO = userService.findOne(user.id).get()
-
-        then:
-        userDTO.id == user.id
-        userDTO.isFollowedByMe == null // not signed in
-    }
-
-    def "can find a user with isFollowedByMe true when signed in"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        User currentUser = userRepository.save(new User(username: "current@test.com", password: "secret", name: "akira"))
-        signIn(currentUser)
-        relationshipRepository.save(new Relationship(follower: currentUser, followed: user))
-
-        when:
-        UserDTO userDTO = userService.findOne(user.id).get()
-
-        then:
-        userDTO.id == user.id
-        userDTO.isFollowedByMe == true
-    }
-
-    def "can find me"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        signIn(user)
-
-        when:
-        UserDTO userDTO = userService.findMe().get()
-
-        then:
-        userDTO.id == user.id
-    }
-
-    def "can find paged user list"() {
-        given:
-        //noinspection GroovyUnusedAssignment
-        User user1 = userRepository.save(new User(username: "test1@test.com", password: "secret", name: "akira"))
-        User user2 = userRepository.save(new User(username: "test2@test.com", password: "secret", name: "akira"))
-
-        when:
-        PageRequest pageRequest = new PageRequest(1, 1)
-        Page<UserDTO> page = userService.findAll(pageRequest)
-
-        then:
-        page.content.first().id == user2.id
-        page.totalElements == 2
-    }
-
-    def "can create a user"() {
-        given:
-        UserParams params = new UserParams("test1@test.com", "secret", "test1")
-
-        when:
-        User user = userService.create(params)
-
-        then:
-        userRepository.count() == 1
-        user.username == "test1@test.com"
-    }
-
-    def "can update a user"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        UserParams params = new UserParams("test2@test.com", "secret2", "test2")
-
-        when:
-        userService.update(user, params)
-
-        then:
-        user.username == "test2@test.com"
-        user.name == "test2"
-
-        when:
-        params = new UserParams("test3@test.com", null, null)
-        userService.update(user, params)
-
-        then:
-        user.username == "test3@test.com"
-        user.name == "test2"
-    }
-
-    def "can update me"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-        UserParams params = new UserParams("test2@test.com", "secret2", "test2")
-        signIn(user)
-
-        when:
-        userService.updateMe(params)
-
-        then:
-        user.username == "test2@test.com"
-        user.name == "test2"
-    }
-
-    def "loadUserByUsername"() {
-        given:
-        User user = userRepository.save(new User(username: "akira@test.com", password: "secret", name: "akira"))
-
-        when:
-        UserDetails userDetails = userService.loadUserByUsername("akira@test.com")
-
-        then:
-        user.username == userDetails.username
-
-        when:
-        userService.loadUserByUsername("test1@test.com")
-
-        then:
-        thrown(UsernameNotFoundException)
-    }
-
-}
diff --git a/src/test/kotlin/com/myapp/auth/SecurityContextServiceTest.kt b/src/test/kotlin/com/myapp/auth/SecurityContextServiceTest.kt
new file mode 100644
index 0000000..e4fcdcd
--- /dev/null
+++ b/src/test/kotlin/com/myapp/auth/SecurityContextServiceTest.kt
@@ -0,0 +1,56 @@
+package com.myapp.auth
+
+import com.myapp.domain.UserDetailsImpl
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContext
+import org.springframework.security.core.context.SecurityContextHolder
+
+class SecurityContextServiceTest {
+
+    private val securityContextService = SecurityContextServiceImpl()
+
+    lateinit private var securityContext: SecurityContext
+
+    @Before
+    fun storeContext() {
+        securityContext = SecurityContextHolder.getContext()
+    }
+
+    @Test
+    fun `currentUser should return current user`() {
+        val userDetails = UserDetailsImpl(TestUser)
+        SecurityContextHolder.setContext(mock {
+            on { authentication } doReturn UserAuthentication(userDetails)
+        })
+
+        val currentUser = securityContextService.currentUser()
+
+        assertThat(currentUser).isNotNull()
+        assertThat(currentUser).isEqualTo(TestUser)
+    }
+
+    @Test
+    fun `currentUser should return null when not signed in`() {
+        SecurityContextHolder.setContext(mock {
+            on { authentication } doReturn mock<Authentication>()
+        })
+
+        val currentUser = securityContextService.currentUser()
+
+        assertThat(currentUser).isNull()
+    }
+
+    @After
+    fun restoreContext() {
+        SecurityContextHolder.setContext(securityContext)
+    }
+
+}
+
diff --git a/src/test/kotlin/com/myapp/auth/TokenAuthenticationServiceTest.kt b/src/test/kotlin/com/myapp/auth/TokenAuthenticationServiceTest.kt
new file mode 100644
index 0000000..7bb7336
--- /dev/null
+++ b/src/test/kotlin/com/myapp/auth/TokenAuthenticationServiceTest.kt
@@ -0,0 +1,44 @@
+package com.myapp.auth
+
+import com.myapp.domain.UserDetailsImpl
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import javax.servlet.http.HttpServletRequest
+
+class TokenAuthenticationServiceTest {
+
+    private val tokenHandler: TokenHandler = mock()
+    private val tokenAuthenticationService by lazy {
+        TokenAuthenticationServiceImpl(
+            tokenHandler = tokenHandler
+        )
+    }
+
+    @Test
+    fun `authentication get authentication from authorization header`() {
+        val mockRequest = mock<HttpServletRequest> {
+            on { getHeader("authorization") } doReturn "Bearer jwt123"
+        }
+        val dummyUserDetails = UserDetailsImpl(TestUser)
+        `when`(tokenHandler.parseUserFromToken("jwt123")).doReturn(dummyUserDetails)
+
+        val authentication = tokenAuthenticationService.authentication(mockRequest)
+
+        assertThat(authentication).isNotNull()
+        assertThat(authentication?.principal).isEqualTo(dummyUserDetails)
+    }
+
+    @Test
+    fun `authentication returns null when no authorization header`() {
+        val mockRequest = mock<HttpServletRequest>()
+
+        val authentication = tokenAuthenticationService.authentication(mockRequest)
+
+        assertThat(authentication).isNull()
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/auth/TokenHandlerTest.kt b/src/test/kotlin/com/myapp/auth/TokenHandlerTest.kt
new file mode 100644
index 0000000..21f3cb0
--- /dev/null
+++ b/src/test/kotlin/com/myapp/auth/TokenHandlerTest.kt
@@ -0,0 +1,55 @@
+package com.myapp.auth
+
+import com.myapp.repository.UserRepository
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import io.jsonwebtoken.Jwts
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import java.util.*
+
+class TokenHandlerTest {
+
+    private val userRepository: UserRepository = mock()
+    private val tokenHandler by lazy {
+        TokenHandlerImpl(
+            secret = "qwerty",
+            userRepository = userRepository
+        )
+    }
+    private val ONE_WEEK = 7 * 24 * 60 * 60 * 1000
+
+    @Test
+    fun `parseUserFromToken should parse user from a token`() {
+        `when`(userRepository.findOne(1))
+            .doReturn(TestUser.copy(
+                username = "test1@test.com"
+            ))
+
+        // This token does not have expiration and has user id = 1
+        val jwt = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIn0.Ew-5gopsJtUKofmP-pwDzWvNBCmCd-VIOMNIPaZXVCfn0AUyWmAce2UJ611X35bWiUOFOfuzcz2g6SK2M4Z9Rg"
+        val userDetails = tokenHandler.parseUserFromToken(jwt)
+
+        assertThat(userDetails.username).isEqualTo("test1@test.com")
+    }
+
+
+    @Test
+    fun `createTokenForUser should create a token for user`() {
+        val beginning = Date()
+        val token = tokenHandler.createTokenForUser(TestUser.copy(_id = 1))
+        val jwt = Jwts.parser()
+            .setSigningKey("qwerty")
+            .parseClaimsJws(token)
+            .body
+        val ending = Date()
+
+        assertThat(jwt.subject.toLong()).isEqualTo(1)
+        assertThat(jwt.expiration.time)
+            // jwt does not have milli secs, so that div(1000).times(1000) is required.
+            .isGreaterThanOrEqualTo(beginning.time.div(1000).times(1000) + ONE_WEEK)
+            .isLessThanOrEqualTo(ending.time.div(1000).times(1000) + ONE_WEEK)
+    }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/AuthControllerTest.kt b/src/test/kotlin/com/myapp/controller/AuthControllerTest.kt
new file mode 100644
index 0000000..41e963b
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/AuthControllerTest.kt
@@ -0,0 +1,76 @@
+package com.myapp.controller
+
+import com.myapp.auth.TokenHandler
+import com.myapp.domain.UserDetailsImpl
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.doReturn
+import org.hamcrest.CoreMatchers.`is`
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.http.MediaType
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@WebMvcTest(AuthController::class)
+class AuthControllerTest : BaseControllerTest() {
+
+    @MockBean
+    lateinit private var tokenHandler: TokenHandler
+
+    @Autowired
+    lateinit private var userDetailsService: UserDetailsService
+
+    @Test
+    fun `auth should auth`() {
+        val userInDb = TestUser.copy(
+            username = "test1@test.com",
+            password = BCryptPasswordEncoder().encode("secret123")
+        )
+        `when`(userDetailsService.loadUserByUsername("test1@test.com"))
+            .doReturn(UserDetailsImpl(userInDb))
+        `when`(tokenHandler.createTokenForUser(userInDb))
+            .doReturn("dummy token")
+
+        perform(post("/api/auth")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj(
+                    "email" to "test1@test.com",
+                    "password" to "secret123"
+                )
+            })
+        ).apply {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$.token", `is`("dummy token")))
+        }
+    }
+
+    @Test
+    fun `auth should not auth when email and password is not valid`() {
+        val userInDb = TestUser.copy(
+            username = "test1@test.com",
+            password = BCryptPasswordEncoder().encode("secret123")
+        )
+        `when`(userDetailsService.loadUserByUsername("test1@test.com"))
+            .doReturn(UserDetailsImpl(userInDb))
+
+        perform(post("/api/auth")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj(
+                    "email" to "test2@test.com",
+                    "password" to "secret123"
+                )
+            })
+        ).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/BaseControllerTest.kt b/src/test/kotlin/com/myapp/controller/BaseControllerTest.kt
new file mode 100644
index 0000000..e37326a
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/BaseControllerTest.kt
@@ -0,0 +1,58 @@
+package com.myapp.controller
+
+import com.beust.klaxon.JSON
+import com.beust.klaxon.JsonObject
+import com.beust.klaxon.json
+import com.myapp.auth.TokenAuthenticationService
+import com.myapp.auth.UserAuthentication
+import com.myapp.domain.User
+import com.myapp.domain.UserDetailsImpl
+import com.myapp.repository.UserRepository
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.any
+import com.nhaarman.mockito_kotlin.doReturn
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.test.context.ActiveProfiles
+import org.springframework.test.context.junit4.SpringRunner
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.RequestBuilder
+import org.springframework.test.web.servlet.ResultActions
+
+@RunWith(SpringRunner::class)
+@ActiveProfiles("test")
+@ComponentScan(basePackages = arrayOf("com.myapp.auth"))
+abstract class BaseControllerTest {
+
+    @Autowired
+    lateinit private var mvc: MockMvc
+
+    @MockBean
+    lateinit private var tokenAuthenticationService: TokenAuthenticationService
+
+    @MockBean
+    lateinit private var userDetailsService: UserDetailsService
+
+    @MockBean
+    lateinit private var userRepository: UserRepository
+
+    fun signIn(): User {
+        `when`(tokenAuthenticationService.authentication(any()))
+            .doReturn(UserAuthentication(UserDetailsImpl(TestUser)))
+
+        return TestUser
+    }
+
+    fun perform(requestBuilder: RequestBuilder): ResultActions {
+        return mvc.perform(requestBuilder)
+    }
+
+    fun jsonString(init: JSON.() -> JsonObject): String {
+        return json(init).toJsonString()
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/FeedControllerTest.kt b/src/test/kotlin/com/myapp/controller/FeedControllerTest.kt
new file mode 100644
index 0000000..41d0be6
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/FeedControllerTest.kt
@@ -0,0 +1,53 @@
+package com.myapp.controller
+
+import com.myapp.service.FeedService
+import com.myapp.testing.TestMicropost
+import com.nhaarman.mockito_kotlin.any
+import com.nhaarman.mockito_kotlin.doReturn
+import org.hamcrest.Matchers.*
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import java.util.*
+
+@WebMvcTest(FeedController::class)
+class FeedControllerTest : BaseControllerTest() {
+
+    @MockBean
+    lateinit private var feedService: FeedService
+
+    @Test
+    fun `findFeed should find feed`() {
+        val user = signIn()
+        val now = Date()
+        `when`(feedService.findFeed(any())).doReturn(listOf(TestMicropost.copy(
+            content = "content1",
+            isMyPost = true,
+            createdAt = now,
+            user = user.copy(
+                username = "test1@test.com"
+            )
+        )))
+
+        perform(get("/api/feed")).apply {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$", hasSize<Any>(1)))
+            andExpect(jsonPath("$[0].content", `is`("content1")))
+            andExpect(jsonPath("$[0].isMyPost", `is`(true)))
+            andExpect(jsonPath("$[0].createdAt", greaterThanOrEqualTo(now.time)))
+            andExpect(jsonPath("$[0].user.email", nullValue()))
+            andExpect(jsonPath("$[0].user.avatarHash", `is`("94fba03762323f286d7c3ca9e001c541")))
+        }
+    }
+
+    @Test
+    fun `findFeed should not find feed when not signed in`() {
+        perform(get("/api/feed")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/MicropostControllerTest.kt b/src/test/kotlin/com/myapp/controller/MicropostControllerTest.kt
new file mode 100644
index 0000000..1d05a45
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/MicropostControllerTest.kt
@@ -0,0 +1,86 @@
+package com.myapp.controller
+
+import com.myapp.service.MicropostService
+import com.myapp.service.exception.NotAuthorizedException
+import com.myapp.testing.TestMicropost
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.doThrow
+import com.nhaarman.mockito_kotlin.verify
+import org.hamcrest.CoreMatchers.`is`
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@WebMvcTest(MicropostController::class)
+class MicropostControllerTest : BaseControllerTest() {
+
+    @MockBean
+    lateinit private var micropostService: MicropostService
+
+    @Test
+    fun `create should create a post`() {
+        signIn()
+        `when`(micropostService.create("my content"))
+            .doReturn(TestMicropost.copy(_id = 1, content = "my content"))
+
+        val response = perform(post("/api/microposts")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj("content" to "my content")
+            })
+        )
+
+        verify(micropostService).create("my content")
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$.id", `is`(1)))
+            andExpect(jsonPath("$.content", `is`("my content")))
+        }
+    }
+
+    @Test
+    fun `create should not create a post when not signed in`() {
+        perform(post("/api/microposts")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj("content" to "my content")
+            })
+        ).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+    @Test
+    fun `delete should delete a post`() {
+        signIn()
+
+        val response = perform(delete("/api/microposts/1"))
+
+        verify(micropostService).delete(1)
+        response.andExpect(status().isOk)
+    }
+
+    @Test
+    fun `delete should not delete a post when not signed in`() {
+        perform(delete("/api/microposts/1")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+    @Test
+    fun `delete should not delete a post which belongs to others`() {
+        signIn()
+        `when`(micropostService.delete(1)).doThrow(NotAuthorizedException(""))
+
+        perform(delete("/api/microposts/1")).apply {
+            andExpect(status().isForbidden)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/RelatedUserControllerTest.kt b/src/test/kotlin/com/myapp/controller/RelatedUserControllerTest.kt
new file mode 100644
index 0000000..693cffb
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/RelatedUserControllerTest.kt
@@ -0,0 +1,73 @@
+package com.myapp.controller
+
+import com.myapp.dto.request.PageParams
+import com.myapp.service.RelatedUserService
+import com.myapp.testing.TestRelatedUser
+import com.nhaarman.mockito_kotlin.*
+import org.assertj.core.api.Assertions.assertThat
+import org.hamcrest.CoreMatchers.`is`
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@WebMvcTest(RelatedUserController::class)
+class RelatedUserControllerTest : BaseControllerTest() {
+
+    @MockBean
+    lateinit private var relatedUserService: RelatedUserService
+
+    @Test
+    fun `followings should list followings`() {
+        `when`(relatedUserService.findFollowings(eq(1), any()))
+            .doReturn(listOf(TestRelatedUser.copy(
+                name = "John Doe",
+                username = "test1@test.com",
+                relationshipId = 101
+            )))
+
+        val response = perform(get("/api/users/1/followings?sinceId=1&maxId=2&count=3"))
+
+        argumentCaptor<PageParams>().apply {
+            verify(relatedUserService).findFollowings(eq(1), capture())
+            assertThat(firstValue.sinceId).isEqualTo(1)
+            assertThat(firstValue.maxId).isEqualTo(2)
+            assertThat(firstValue.count).isEqualTo(3)
+        }
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$[0].name", `is`("John Doe")))
+            andExpect(jsonPath("$[0].avatarHash", `is`("94fba03762323f286d7c3ca9e001c541")))
+            andExpect(jsonPath("$[0].relationshipId", `is`(101)))
+        }
+    }
+
+    @Test
+    fun `followers should list followers`() {
+        `when`(relatedUserService.findFollowers(eq(1), any()))
+            .doReturn(listOf(TestRelatedUser.copy(
+                name = "John Doe",
+                username = "test1@test.com",
+                relationshipId = 101
+            )))
+
+        val response = perform(get("/api/users/1/followers?sinceId=1&maxId=2&count=3"))
+
+        argumentCaptor<PageParams>().apply {
+            verify(relatedUserService).findFollowers(eq(1), capture())
+            assertThat(firstValue.sinceId).isEqualTo(1)
+            assertThat(firstValue.maxId).isEqualTo(2)
+            assertThat(firstValue.count).isEqualTo(3)
+        }
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$[0].name", `is`("John Doe")))
+            andExpect(jsonPath("$[0].avatarHash", `is`("94fba03762323f286d7c3ca9e001c541")))
+            andExpect(jsonPath("$[0].relationshipId", `is`(101)))
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/RelationshipControllerTest.kt b/src/test/kotlin/com/myapp/controller/RelationshipControllerTest.kt
new file mode 100644
index 0000000..50fc07a
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/RelationshipControllerTest.kt
@@ -0,0 +1,78 @@
+package com.myapp.controller
+
+import com.myapp.repository.exception.RelationshipDuplicatedException
+import com.myapp.service.RelationshipService
+import com.myapp.service.exception.RelationshipNotFoundException
+import com.nhaarman.mockito_kotlin.doThrow
+import com.nhaarman.mockito_kotlin.verify
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@WebMvcTest(RelationshipController::class)
+class RelationshipControllerTest : BaseControllerTest() {
+
+    @MockBean
+    lateinit private var relationshipService: RelationshipService
+
+    @Test
+    fun `follow should follow a user`() {
+        signIn()
+
+        val response = perform(post("/api/relationships/to/1"))
+
+        verify(relationshipService).follow(1)
+        response.andExpect(status().isOk)
+    }
+
+    @Test
+    fun `follow should not follow a user when not signed in`() {
+        perform(post("/api/relationships/to/1")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+    @Test
+    fun `follow should not follow when already followed`() {
+        signIn()
+        `when`(relationshipService.follow(1))
+            .doThrow(RelationshipDuplicatedException(""))
+
+        perform(post("/api/relationships/to/1")).apply {
+            andExpect(status().isBadRequest)
+        }
+    }
+
+    @Test
+    fun `unfollow should unfollow a user`() {
+        signIn()
+
+        val response = perform(delete("/api/relationships/to/1"))
+
+        verify(relationshipService).unfollow(1)
+        response.andExpect(status().isOk)
+    }
+
+    @Test
+    fun `unfollow should not unfollow a user when not signed in`() {
+        perform(delete("/api/relationships/to/1")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+    @Test
+    fun `unfollow should not unfollow when relationship not found`() {
+        signIn()
+        `when`(relationshipService.unfollow(1))
+            .doThrow(RelationshipNotFoundException())
+
+        perform(delete("/api/relationships/to/1")).apply {
+            andExpect(status().isNotFound)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/UserControllerTest.kt b/src/test/kotlin/com/myapp/controller/UserControllerTest.kt
new file mode 100644
index 0000000..db9edaf
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/UserControllerTest.kt
@@ -0,0 +1,254 @@
+package com.myapp.controller
+
+import com.myapp.domain.UserStats
+import com.myapp.dto.page.PageImpl
+import com.myapp.dto.request.UserEditParams
+import com.myapp.dto.request.UserNewParams
+import com.myapp.repository.exception.EmailDuplicatedException
+import com.myapp.repository.exception.RecordNotFoundException
+import com.myapp.service.UserService
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.*
+import org.assertj.core.api.Assertions.assertThat
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.Matchers.hasSize
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.slf4j.LoggerFactory
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@WebMvcTest(UserController::class)
+class UserControllerTest : BaseControllerTest() {
+
+    @Suppress("unused")
+    private val logger = LoggerFactory.getLogger(UserControllerTest::class.java)
+
+    @MockBean
+    lateinit private var userService: UserService
+
+    @Test
+    fun `list should list users`() {
+        signIn()
+        `when`(userService.findAll(101, 1001))
+            .doReturn(PageImpl(
+                totalPages = 1,
+                content = listOf(TestUser.copy(
+                    username = "test1@test.com",
+                    name = "John Doe"
+                ))
+            ))
+
+        perform(get("/api/users?page=101&size=1001")).apply {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$.content").exists())
+            andExpect(jsonPath("$.content", hasSize<Any>(1)))
+            andExpect(jsonPath("$.content[0].name", `is`("John Doe")))
+            andExpect(jsonPath("$.content[0].email", nullValue()))
+            andExpect(jsonPath("$.content[0].avatarHash", `is`("94fba03762323f286d7c3ca9e001c541")))
+        }
+    }
+
+    @Test
+    fun `list should not list when not signed in`() {
+        perform(get("/api/users")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+    @Test
+    fun `create should create a user`() {
+        `when`(userService.create(any()))
+            .doReturn(TestUser.copy(
+                _id = 1,
+                username = "test1@test.com",
+                name = "John Doe"
+            ))
+
+        val response = perform(post("/api/users")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj(
+                    "email" to "test1@test.com",
+                    "password" to "secret123",
+                    "name" to "John Doe"
+                )
+            })
+        )
+
+        argumentCaptor<UserNewParams>().apply {
+            verify(userService).create(capture())
+            assertThat(firstValue.email).isEqualTo("test1@test.com")
+            assertThat(firstValue.password).isEqualTo("secret123")
+            assertThat(firstValue.name).isEqualTo("John Doe")
+        }
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$.id", `is`(1)))
+            andExpect(jsonPath("$.email", nullValue()))
+            andExpect(jsonPath("$.name", `is`("John Doe")))
+            andExpect(jsonPath("$.avatarHash", `is`("94fba03762323f286d7c3ca9e001c541")))
+        }
+    }
+
+    @Test
+    fun `create should not create a user when password is too short`() {
+        perform(post("/api/users")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj(
+                    "email" to "test1@test.com",
+                    "password" to "a",
+                    "name" to "John Doe"
+                )
+            })
+        ).apply {
+            andExpect(status().isBadRequest)
+        }
+    }
+
+    @Test
+    fun `create should not create a user when email is duplicated`() {
+        `when`(userService.create(any()))
+            .doThrow(EmailDuplicatedException(""))
+
+        perform(post("/api/users")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj(
+                    "email" to "test1@test.com",
+                    "password" to "secret123",
+                    "name" to "John Doe"
+                )
+            })
+        ).apply {
+            andExpect(status().isBadRequest)
+            andExpect(jsonPath("$.code", `is`("email_already_taken")))
+        }
+    }
+
+    @Test
+    fun `show should show a user`() {
+        `when`(userService.findOne(1)).doReturn(TestUser.copy(
+            name = "John Doe",
+            username = "test1@test.com",
+            userStats = UserStats(
+                followerCnt = 1,
+                followingCnt = 2,
+                micropostCnt = 3
+            )
+        ))
+
+        val response = perform(get("/api/users/1"))
+
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$.name", `is`("John Doe")))
+            andExpect(jsonPath("$.email", nullValue()))
+            andExpect(jsonPath("$.avatarHash", `is`("94fba03762323f286d7c3ca9e001c541")))
+            andExpect(jsonPath("$.isFollowedByMe", nullValue()))
+            andExpect(jsonPath("$.userStats").exists())
+            andExpect(jsonPath("$.userStats.micropostCnt", `is`(3)))
+            andExpect(jsonPath("$.userStats.followingCnt", `is`(2)))
+            andExpect(jsonPath("$.userStats.followerCnt", `is`(1)))
+        }
+    }
+
+    @Test
+    fun `show should not show a user when user not found`() {
+        `when`(userService.findOne(1)).doThrow(RecordNotFoundException())
+
+        perform(get("/api/users/1")).apply {
+            andExpect(status().isNotFound)
+        }
+    }
+
+    @Test
+    fun `showMe should show me`() {
+        signIn()
+        `when`(userService.findMe()).doReturn(TestUser.copy(
+            name = "John Doe",
+            username = "test1@test.com",
+            userStats = UserStats(
+                followerCnt = 1,
+                followingCnt = 2,
+                micropostCnt = 3
+            )
+        ))
+
+        val response = perform(get("/api/users/me"))
+
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$.name", `is`("John Doe")))
+            andExpect(jsonPath("$.email", nullValue()))
+            andExpect(jsonPath("$.avatarHash", `is`("94fba03762323f286d7c3ca9e001c541")))
+            andExpect(jsonPath("$.isFollowedByMe", nullValue()))
+            andExpect(jsonPath("$.userStats").exists())
+            andExpect(jsonPath("$.userStats.micropostCnt", `is`(3)))
+            andExpect(jsonPath("$.userStats.followingCnt", `is`(2)))
+            andExpect(jsonPath("$.userStats.followerCnt", `is`(1)))
+        }
+    }
+
+    @Test
+    fun `showMe should not show me when not signed in`() {
+        perform(get("/api/users/me")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+    @Test
+    fun `updateMe should update me`() {
+        signIn()
+
+        perform(patch("/api/users/me")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj(
+                    "email" to "test1@test.com",
+                    "password" to "secret123",
+                    "name" to "John Doe"
+                )
+            })
+        )
+
+        argumentCaptor<UserEditParams>().apply {
+            verify(userService).updateMe(capture())
+            assertThat(firstValue.email).isEqualTo("test1@test.com")
+            assertThat(firstValue.password).isEqualTo("secret123")
+            assertThat(firstValue.name).isEqualTo("John Doe")
+        }
+    }
+
+    @Test
+    fun `updateMe should not update me when not signed in`() {
+        perform(patch("/api/users/me")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+    @Test
+    fun `updateMe should not update me when password is too short`() {
+        signIn()
+
+        perform(patch("/api/users/me")
+            .contentType(MediaType.APPLICATION_JSON)
+            .content(jsonString {
+                obj(
+                    "email" to "test1@test.com",
+                    "password" to "a",
+                    "name" to "John Doe"
+                )
+            })
+        ).apply {
+            andExpect(status().isBadRequest)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/controller/UserMicropostControllerTest.kt b/src/test/kotlin/com/myapp/controller/UserMicropostControllerTest.kt
new file mode 100644
index 0000000..29e276b
--- /dev/null
+++ b/src/test/kotlin/com/myapp/controller/UserMicropostControllerTest.kt
@@ -0,0 +1,87 @@
+package com.myapp.controller
+
+import com.myapp.dto.request.PageParams
+import com.myapp.service.MicropostService
+import com.myapp.testing.TestMicropost
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.*
+import org.assertj.core.api.Assertions.assertThat
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.Matchers.hasSize
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@WebMvcTest(UserMicropostController::class)
+class UserMicropostControllerTest : BaseControllerTest() {
+
+    @MockBean
+    lateinit private var micropostService: MicropostService
+
+    @Test
+    fun `list should list user's posts`() {
+        `when`(micropostService.findAllByUser(eq(1), any()))
+            .doReturn(listOf(TestMicropost.copy(
+                content = "my content",
+                user = TestUser.copy(
+                    name = "John Doe"
+                )
+            )))
+
+        val response = perform(get("/api/users/1/microposts/?sinceId=1&maxId=2&count=3"))
+
+        argumentCaptor<PageParams>().apply {
+            verify(micropostService).findAllByUser(eq(1), capture())
+            assertThat(firstValue.sinceId).isEqualTo(1)
+            assertThat(firstValue.maxId).isEqualTo(2)
+            assertThat(firstValue.count).isEqualTo(3)
+        }
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$", hasSize<Any>(1)))
+            andExpect(jsonPath("$[0].content", `is`("my content")))
+            andExpect(jsonPath("$[0].isMyPost", nullValue()))
+            andExpect(jsonPath("$[0].user.name", `is`("John Doe")))
+        }
+    }
+
+    @Test
+    fun `listMyPosts should list my posts`() {
+        `when`(micropostService.findMyPosts(any()))
+            .doReturn(listOf(TestMicropost.copy(
+                content = "my content",
+                user = TestUser.copy(
+                    name = "John Doe"
+                )
+            )))
+
+        val response = perform(get("/api/users/me/microposts/?sinceId=1&maxId=2&count=3"))
+
+        argumentCaptor<PageParams>().apply {
+            verify(micropostService).findMyPosts(capture())
+            assertThat(firstValue.sinceId).isEqualTo(1)
+            assertThat(firstValue.maxId).isEqualTo(2)
+            assertThat(firstValue.count).isEqualTo(3)
+        }
+        with(response) {
+            andExpect(status().isOk)
+            andExpect(jsonPath("$", hasSize<Any>(1)))
+            andExpect(jsonPath("$[0].content", `is`("my content")))
+            andExpect(jsonPath("$[0].isMyPost", nullValue()))
+            andExpect(jsonPath("$[0].user.name", `is`("John Doe")))
+        }
+    }
+
+    @Test
+    fun `listMyPosts should not list my posts when not signed in`() {
+        perform(get("/api/users/me/microposts")).apply {
+            andExpect(status().isUnauthorized)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/repository/BaseRepositoryTest.kt b/src/test/kotlin/com/myapp/repository/BaseRepositoryTest.kt
new file mode 100644
index 0000000..eb321ff
--- /dev/null
+++ b/src/test/kotlin/com/myapp/repository/BaseRepositoryTest.kt
@@ -0,0 +1,45 @@
+package com.myapp.repository
+
+import org.junit.runner.RunWith
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration
+import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
+import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
+import org.springframework.boot.test.context.SpringBootTestContextBootstrapper
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.Configuration
+import org.springframework.jdbc.datasource.DataSourceTransactionManager
+import org.springframework.test.context.ActiveProfiles
+import org.springframework.test.context.BootstrapWith
+import org.springframework.test.context.junit4.SpringRunner
+import org.springframework.transaction.PlatformTransactionManager
+import org.springframework.transaction.annotation.Transactional
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
+import javax.sql.DataSource
+import javax.validation.Validator
+
+@ActiveProfiles("test")
+@RunWith(SpringRunner::class)
+@BootstrapWith(SpringBootTestContextBootstrapper::class)
+@Transactional
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@ComponentScan(basePackages = arrayOf("com.myapp.repository"))
+@ImportAutoConfiguration(FlywayAutoConfiguration::class, JooqAutoConfiguration::class)
+abstract class BaseRepositoryTest {
+
+    @Configuration
+    class RepositoryTestConfig {
+        @Bean
+        fun validator(): Validator {
+            return LocalValidatorFactoryBean()
+        }
+
+        @Bean
+        @Suppress("SpringKotlinAutowiring")
+        fun transactionManager(dataSource: DataSource): PlatformTransactionManager {
+            return DataSourceTransactionManager(dataSource)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/repository/FeedRepositoryTest.kt b/src/test/kotlin/com/myapp/repository/FeedRepositoryTest.kt
new file mode 100644
index 0000000..3c8ceaf
--- /dev/null
+++ b/src/test/kotlin/com/myapp/repository/FeedRepositoryTest.kt
@@ -0,0 +1,71 @@
+package com.myapp.repository
+
+import com.myapp.dto.request.PageParams
+import com.myapp.generated.tables.Micropost.MICROPOST
+import com.myapp.generated.tables.Relationship.RELATIONSHIP
+import com.myapp.generated.tables.User.USER
+import org.assertj.core.api.Assertions.assertThat
+import org.jooq.DSLContext
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+
+class FeedRepositoryTest : BaseRepositoryTest() {
+
+    @Autowired
+    lateinit var dsl: DSLContext
+
+    @Autowired
+    lateinit var feedRepository: FeedRepository
+
+    @Test
+    fun `findFeed should find a feed`() {
+        populateTestData()
+
+        val feed = feedRepository.findFeed(1, PageParams())
+
+        assertThat(feed.size).isEqualTo(3)
+        assertThat(feed.first().content).isEqualTo("user 2 - content1")
+        assertThat(feed.last().content).isEqualTo("user 1 - content1")
+    }
+
+    @Test
+    fun `findFeed should find a feed with page params`() {
+        populateTestData()
+
+        feedRepository.findFeed(1, PageParams(sinceId = 2)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().content).isEqualTo("user 2 - content1")
+        }
+        feedRepository.findFeed(1, PageParams(maxId = 2)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().content).isEqualTo("user 1 - content1")
+        }
+        feedRepository.findFeed(1, PageParams(count = 2)).apply {
+            assertThat(size).isEqualTo(2)
+        }
+    }
+
+    private fun populateTestData() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1")
+                .values(2, "test2@test.com", "secret123", "user 2")
+                .values(3, "test3@test.com", "secret123", "user 3")
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, FOLLOWER_ID, FOLLOWED_ID)
+                .values(1, 2)
+                .execute()
+        }
+        with(MICROPOST) {
+            dsl.insertInto(this, ID, CONTENT, USER_ID)
+                .values(1, "user 1 - content1", 1)
+                .values(2, "user 1 - content2", 1)
+                .values(3, "user 2 - content1", 2)
+                .values(4, "user 3 - content1", 3) // It won't be included in a feed of user 1.
+                .execute()
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/repository/MicropostRepositoryTest.kt b/src/test/kotlin/com/myapp/repository/MicropostRepositoryTest.kt
new file mode 100644
index 0000000..0791eb4
--- /dev/null
+++ b/src/test/kotlin/com/myapp/repository/MicropostRepositoryTest.kt
@@ -0,0 +1,125 @@
+package com.myapp.repository
+
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+import com.myapp.generated.tables.Micropost.MICROPOST
+import com.myapp.generated.tables.User.USER
+import com.myapp.repository.exception.RecordNotFoundException
+import com.myapp.testing.TestUser
+import org.assertj.core.api.Assertions.assertThat
+import org.jooq.DSLContext
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import kotlin.test.assertFailsWith
+
+
+class MicropostRepositoryTest : BaseRepositoryTest() {
+
+    @Autowired
+    lateinit var dsl: DSLContext
+
+    @Autowired
+    lateinit var micropostRepository: MicropostRepository
+
+    @Test
+    fun `findOne should find a post`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .execute()
+        dsl.insertInto(MICROPOST, MICROPOST.ID, MICROPOST.CONTENT, MICROPOST.USER_ID)
+            .values(101, "my content", 1)
+            .execute()
+
+        val post = micropostRepository.findOne(101)
+
+        assertThat(post).isNotNull()
+        assertThat(post.content).isEqualTo("my content")
+        assertThat(post.user.id).isEqualTo(1)
+    }
+
+    @Test
+    fun `findOne should throw when no record`() {
+        assertFailsWith<RecordNotFoundException> {
+            micropostRepository.findOne(1)
+        }
+    }
+
+    @Test
+    fun `findAllByUser should find posts by user`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "user1")
+            .values(2, "test2@test.com", "secret123", "user2")
+            .execute()
+        dsl.insertInto(MICROPOST, MICROPOST.ID, MICROPOST.CONTENT, MICROPOST.USER_ID)
+            .values(101, "my content 1", 1)
+            .values(102, "my content 2", 1)
+            .values(103, "user2 content 1", 2)
+            .execute()
+
+        val posts = micropostRepository.findAllByUser(1, PageParams())
+
+        assertThat(posts.size).isEqualTo(2)
+        assertThat(posts.first().content).isEqualTo("my content 2")
+        assertThat(posts.last().content).isEqualTo("my content 1")
+        assertThat(posts.first().user.id).isEqualTo(1)
+    }
+
+    @Test
+    fun `findAllByUser should find posts with page params`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "user1")
+            .execute()
+        dsl.insertInto(MICROPOST, MICROPOST.ID, MICROPOST.CONTENT, MICROPOST.USER_ID)
+            .values(101, "my content 1", 1)
+            .values(102, "my content 2", 1)
+            .values(103, "my content 3", 1)
+            .execute()
+
+        micropostRepository.findAllByUser(1, PageParams(sinceId = 102)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().content).isEqualTo("my content 3")
+        }
+        micropostRepository.findAllByUser(1, PageParams(maxId = 102)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().content).isEqualTo("my content 1")
+        }
+        micropostRepository.findAllByUser(1, PageParams(count = 2)).apply {
+            assertThat(size).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun `create should create a post`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .execute()
+
+        val createdPost = micropostRepository.create(Micropost(
+            content = "my content",
+            user = TestUser.copy(_id = 1)
+        ))
+        val postRecord = dsl.fetchOne(MICROPOST)
+        val count = dsl.fetchCount(MICROPOST)
+
+        assertThat(createdPost).isNotNull()
+        assertThat(createdPost.id).isEqualTo(postRecord.id)
+        assertThat(count).isEqualTo(1)
+    }
+
+    @Test
+    fun `delete should delete a post`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .execute()
+        dsl.insertInto(MICROPOST, MICROPOST.ID, MICROPOST.CONTENT, MICROPOST.USER_ID)
+            .values(101, "my content", 1)
+            .execute()
+
+        micropostRepository.delete(101)
+        val count = dsl.fetchCount(MICROPOST)
+
+        assertThat(count).isEqualTo(0)
+    }
+
+}
+
diff --git a/src/test/kotlin/com/myapp/repository/RelatedUserRepositoryTest.kt b/src/test/kotlin/com/myapp/repository/RelatedUserRepositoryTest.kt
new file mode 100644
index 0000000..28267d7
--- /dev/null
+++ b/src/test/kotlin/com/myapp/repository/RelatedUserRepositoryTest.kt
@@ -0,0 +1,128 @@
+package com.myapp.repository
+
+import com.myapp.dto.request.PageParams
+import com.myapp.generated.tables.Relationship.RELATIONSHIP
+import com.myapp.generated.tables.User.USER
+import org.assertj.core.api.Assertions.assertThat
+import org.jooq.DSLContext
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+
+class RelatedUserRepositoryTest : BaseRepositoryTest() {
+
+    @Autowired
+    lateinit var dsl: DSLContext
+
+    @Autowired
+    lateinit var relatedUserRepository: RelatedUserRepository
+
+    @Test
+    fun `findFollowers should find followers`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1") // subject
+                .values(2, "test2@test.com", "secret123", "user 2") // follower
+                .values(3, "test3@test.com", "secret123", "user 3") // follower
+                .values(4, "test4@test.com", "secret123", "user 4") // not follower
+                .execute()
+        }
+        with(RELATIONSHIP) { dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID) .values(102, 2, 1)
+                .values(103, 3, 1)
+                .execute()
+        }
+
+        val followers = relatedUserRepository.findFollowers(1, PageParams())
+
+        assertThat(followers.size).isEqualTo(2)
+        assertThat(followers.first().name).isEqualTo("user 3")
+        assertThat(followers.first().relationshipId).isEqualTo(103)
+        assertThat(followers.last().relationshipId).isEqualTo(102)
+    }
+
+    @Test
+    fun `findFollowers should find followers with page params`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1")
+                .values(2, "test2@test.com", "secret123", "user 2")
+                .values(3, "test3@test.com", "secret123", "user 3")
+                .values(4, "test4@test.com", "secret123", "user 4")
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID)
+                .values(102, 2, 1)
+                .values(103, 3, 1)
+                .values(104, 4, 1)
+                .execute()
+        }
+
+        relatedUserRepository.findFollowers(1, PageParams(sinceId = 103)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().username).isEqualTo("test4@test.com")
+        }
+        relatedUserRepository.findFollowers(1, PageParams(maxId = 103)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().username).isEqualTo("test2@test.com")
+        }
+        relatedUserRepository.findFollowers(1, PageParams(count = 2)).apply {
+            assertThat(size).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun `findFollowings should find followings`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1") // subject
+                .values(2, "test2@test.com", "secret123", "user 2") // followed
+                .values(3, "test3@test.com", "secret123", "user 3") // followed
+                .values(4, "test4@test.com", "secret123", "user 4") // not followed
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID)
+                .values(102, 1, 2)
+                .values(103, 1, 3)
+                .execute()
+        }
+
+        val followings = relatedUserRepository.findFollowings(1, PageParams())
+
+        assertThat(followings.size).isEqualTo(2)
+        assertThat(followings.first().name).isEqualTo("user 3")
+        assertThat(followings.first().relationshipId).isEqualTo(103)
+        assertThat(followings.last().relationshipId).isEqualTo(102)
+    }
+
+    @Test
+    fun `findFollowings should find followings with page params`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1")
+                .values(2, "test2@test.com", "secret123", "user 2")
+                .values(3, "test3@test.com", "secret123", "user 3")
+                .values(4, "test4@test.com", "secret123", "user 4")
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID)
+                .values(102, 1, 2)
+                .values(103, 1, 3)
+                .values(104, 1, 4)
+                .execute()
+        }
+
+        relatedUserRepository.findFollowings(1, PageParams(sinceId = 103)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().username).isEqualTo("test4@test.com")
+        }
+        relatedUserRepository.findFollowings(1, PageParams(maxId = 103)).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().username).isEqualTo("test2@test.com")
+        }
+        relatedUserRepository.findFollowings(1, PageParams(count = 2)).apply {
+            assertThat(size).isEqualTo(2)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/repository/RelationshipRepositoryTest.kt b/src/test/kotlin/com/myapp/repository/RelationshipRepositoryTest.kt
new file mode 100644
index 0000000..c618fc9
--- /dev/null
+++ b/src/test/kotlin/com/myapp/repository/RelationshipRepositoryTest.kt
@@ -0,0 +1,145 @@
+package com.myapp.repository
+
+import com.myapp.domain.Relationship
+import com.myapp.generated.tables.Relationship.RELATIONSHIP
+import com.myapp.generated.tables.User.USER
+import com.myapp.repository.exception.RelationshipDuplicatedException
+import com.myapp.testing.TestUser
+import org.assertj.core.api.Assertions.assertThat
+import org.jooq.DSLContext
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import kotlin.test.assertFailsWith
+
+class RelationshipRepositoryTest : BaseRepositoryTest() {
+
+    @Autowired
+    lateinit var dsl: DSLContext
+
+    @Autowired
+    lateinit var relationshipRepository: RelationshipRepository
+
+    @Test
+    fun `findOneByFollowerAndFollowed should find a relationship by follower and followed`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1") // follower
+                .values(2, "test2@test.com", "secret123", "user 2") // followed
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID)
+                .values(101, 1, 2)
+                .execute()
+        }
+
+        val relationship = relationshipRepository.findOneByFollowerAndFollowed(1, 2)
+
+        assertThat(relationship).isNotNull()
+        assertThat(relationship?.id).isEqualTo(101)
+        assertThat(relationship?.follower?.id).isEqualTo(1)
+        assertThat(relationship?.followed?.id).isEqualTo(2)
+    }
+
+    @Test
+    fun `findOneByFollowerAndFollowed should be null when no matched relationship`() {
+        val relationship = relationshipRepository.findOneByFollowerAndFollowed(1, 2)
+
+        assertThat(relationship).isNull()
+    }
+
+    @Test
+    fun `findAllByFollowerAndFollowedUsers should find relationships by follower and followed users`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1") // follower
+                .values(2, "test2@test.com", "secret123", "user 2") // followed
+                .values(3, "test3@test.com", "secret123", "user 3") // followed
+                .values(4, "test4@test.com", "secret123", "user 4") // followed
+                .values(5, "test5@test.com", "secret123", "user 5") // others
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID)
+                .values(101, 1, 2)
+                .values(102, 1, 3)
+                .execute()
+        }
+
+        val relationships = relationshipRepository
+            .findAllByFollowerAndFollowedUsers(1, listOf(2, 3, 5))
+
+        assertThat(relationships.size).isEqualTo(2)
+        assertThat(relationships.first().id).isEqualTo(101)
+        assertThat(relationships.first().follower.id).isEqualTo(1)
+        assertThat(relationships.first().followed.id).isEqualTo(2)
+        assertThat(relationships.last().id).isEqualTo(102)
+        assertThat(relationships.last().follower.id).isEqualTo(1)
+        assertThat(relationships.last().followed.id).isEqualTo(3)
+    }
+
+    @Test
+    fun `create should create a relationship`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1") // follower
+                .values(2, "test2@test.com", "secret123", "user 2") // followed
+                .execute()
+        }
+
+        val createdRelationship = relationshipRepository.create(Relationship(
+            follower = TestUser.copy(_id = 1),
+            followed = TestUser.copy(_id = 2)
+        ))
+        val relationshipRecord = dsl.fetchOne(RELATIONSHIP)
+        val count = dsl.fetchCount(RELATIONSHIP)
+
+        assertThat(createdRelationship).isNotNull()
+        assertThat(createdRelationship.id).isEqualTo(relationshipRecord.id)
+        assertThat(count).isEqualTo(1)
+    }
+
+
+    @Test
+    fun `create should throw when relationship is duplicated`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1") // follower
+                .values(2, "test2@test.com", "secret123", "user 2") // followed
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID)
+                .values(101, 1, 2)
+                .execute()
+        }
+
+        assertFailsWith<RelationshipDuplicatedException> {
+            relationshipRepository.create(Relationship(
+                follower = TestUser.copy(_id = 1),
+                followed = TestUser.copy(_id = 2)
+            ))
+        }
+    }
+
+    @Test
+    fun `delete should delete a relationship`() {
+        with(USER) {
+            dsl.insertInto(this, ID, USERNAME, PASSWORD, NAME)
+                .values(1, "test1@test.com", "secret123", "user 1") // follower
+                .values(2, "test2@test.com", "secret123", "user 2") // followed
+                .execute()
+        }
+        with(RELATIONSHIP) {
+            dsl.insertInto(this, ID, FOLLOWER_ID, FOLLOWED_ID)
+                .values(101, 1, 2)
+                .execute()
+        }
+
+        relationshipRepository.delete(101)
+        val count = dsl.fetchCount(RELATIONSHIP)
+
+        assertThat(count).isEqualTo(0)
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/repository/UserRepositoryTest.kt b/src/test/kotlin/com/myapp/repository/UserRepositoryTest.kt
new file mode 100644
index 0000000..30cff3d
--- /dev/null
+++ b/src/test/kotlin/com/myapp/repository/UserRepositoryTest.kt
@@ -0,0 +1,203 @@
+package com.myapp.repository
+
+import com.myapp.domain.User
+import com.myapp.generated.tables.User.USER
+import com.myapp.repository.exception.EmailDuplicatedException
+import com.myapp.repository.exception.RecordInvalidException
+import com.myapp.repository.exception.RecordNotFoundException
+import org.assertj.core.api.Assertions.assertThat
+import org.jooq.DSLContext
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import kotlin.test.assertFailsWith
+
+
+class UserRepositoryTest : BaseRepositoryTest() {
+
+    @Autowired
+    lateinit var dsl: DSLContext
+
+    @Autowired
+    lateinit var userRepository: UserRepository
+
+    @Test
+    fun `findOne should find a user`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .execute()
+
+        val user = userRepository.findOne(1)
+
+        assertThat(user).isNotNull()
+        assertThat(user.username).isEqualTo("test1@test.com")
+    }
+
+    @Test
+    fun `findOne should throw when no record`() {
+        assertFailsWith<RecordNotFoundException> {
+            userRepository.findOne(1)
+        }
+    }
+
+    @Test
+    fun `findOneWithStats should find a user with stats`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .returning(USER.ID)
+            .fetchOne()
+
+        val user = userRepository.findOneWithStats(1)
+
+        assertThat(user).isNotNull()
+        assertThat(user.userStats).isNotNull()
+        assertThat(user.userStats?.micropostCnt).isEqualTo(0)
+    }
+
+    @Test
+    fun `findOneWithStats should throw when no record`() {
+        assertFailsWith<RecordNotFoundException> {
+            userRepository.findOneWithStats(1)
+        }
+    }
+
+    @Test
+    fun `findOneByUsername should find user by name`() {
+        dsl.insertInto(USER, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values("test1@test.com", "secret123", "test1")
+            .execute()
+
+        val user = userRepository.findOneByUsername("test1@test.com")
+
+        assertThat(user).isNotNull()
+        assertThat(user?.username).isEqualTo("test1@test.com")
+    }
+
+    @Test
+    fun `findOneByUsername should be null when no matched user`() {
+        val user = userRepository.findOneByUsername("test1@test.com")
+
+        assertThat(user).isNull()
+    }
+
+    @Test
+    fun `findAll should find users`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .values(2, "test2@test.com", "secret123", "test2")
+            .execute()
+
+        val page = userRepository.findAll(page = 1, size = 10)
+
+        assertThat(page.content.size).isEqualTo(2)
+        assertThat(page.content.first().username).isEqualTo("test1@test.com")
+        assertThat(page.content.last().username).isEqualTo("test2@test.com")
+        assertThat(page.totalPages).isEqualTo(1)
+    }
+
+    @Test
+    fun `findAll should find users on page 2`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .values(2, "test2@test.com", "secret123", "test2")
+            .execute()
+
+        val page = userRepository.findAll(page = 2, size = 1)
+
+        assertThat(page.content.size).isEqualTo(1)
+        assertThat(page.content.first().username).isEqualTo("test2@test.com")
+        assertThat(page.totalPages).isEqualTo(2)
+    }
+
+    @Test
+    fun `create should create a user`() {
+        val createdUser = userRepository.create(User(
+            username = "test1@test.com",
+            password = "secret123",
+            name = "test1"
+        ))
+        val userRecord = dsl.fetchOne(USER)
+        val count = dsl.fetchCount(USER)
+
+        assertThat(createdUser).isNotNull()
+        assertThat(createdUser.id).isEqualTo(userRecord.id)
+        assertThat(count).isEqualTo(1)
+    }
+
+    @Test
+    fun `create should not create a user when it is invalid`() {
+        assertFailsWith<RecordInvalidException> {
+            userRepository.create(User(
+                username = "test1 AT test.com",
+                password = "secret123",
+                name = "test1"
+            ))
+        }
+    }
+
+    @Test
+    fun `create should throw when email is duplicated`() {
+        dsl.insertInto(USER, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values("test1@test.com", "secret123", "test1")
+            .execute()
+
+        assertFailsWith<EmailDuplicatedException> {
+            userRepository.create(User(
+                username = "test1@test.com",
+                password = "secret123",
+                name = "test1"
+            ))
+        }
+    }
+
+    @Test
+    fun `update should update a user`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .execute()
+
+        userRepository.update(User(
+            _id = 1,
+            username = "test1@test.com",
+            password = "secret123",
+            name = "test2"
+        ))
+        val userRecord = dsl.fetchOne(USER)
+
+        assertThat(userRecord.name).isEqualTo("test2")
+    }
+
+    @Test
+    fun `update should not update a user when it is invalid`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .execute()
+
+        assertFailsWith<RecordInvalidException> {
+            userRepository.update(User(
+                _id = 1,
+                username = "test1 AT test.com",
+                password = "secret123",
+                name = "test2"
+            ))
+        }
+    }
+
+    @Test
+    fun `update should throw when email is duplicated`() {
+        dsl.insertInto(USER, USER.ID, USER.USERNAME, USER.PASSWORD, USER.NAME)
+            .values(1, "test1@test.com", "secret123", "test1")
+            .values(2, "test2@test.com", "secret123", "test2")
+            .execute()
+
+        assertFailsWith<EmailDuplicatedException> {
+            userRepository.update(User(
+                _id = 2,
+                username = "test1@test.com",
+                password = "secret123",
+                name = "test2"
+            ))
+        }
+    }
+
+}
+
diff --git a/src/test/kotlin/com/myapp/service/FeedServiceTest.kt b/src/test/kotlin/com/myapp/service/FeedServiceTest.kt
new file mode 100644
index 0000000..0c5dd9e
--- /dev/null
+++ b/src/test/kotlin/com/myapp/service/FeedServiceTest.kt
@@ -0,0 +1,55 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.dto.request.PageParams
+import com.myapp.repository.FeedRepository
+import com.myapp.testing.TestMicropost
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.security.access.AccessDeniedException
+import kotlin.test.assertFailsWith
+
+class FeedServiceTest {
+
+    private val feedRepository: FeedRepository = mock()
+    private val securityContextService: SecurityContextService = mock()
+    private val feedService by lazy {
+        FeedServiceImpl(
+            feedRepository = feedRepository,
+            securityContextService = securityContextService
+        )
+    }
+
+    @Test
+    fun `findFeed should find feed`() {
+        `when`(securityContextService.currentUser())
+            .doReturn(TestUser.copy(_id = 1))
+        `when`(feedRepository.findFeed(1, PageParams()))
+            .doReturn(listOf(
+                TestMicropost.copy(
+                    user = TestUser.copy(_id = 1)
+                ),
+                TestMicropost.copy(
+                    user = TestUser.copy(_id = 2)
+                )
+            ))
+
+        val feed = feedService.findFeed(PageParams())
+
+        assertThat(feed.size).isEqualTo(2)
+        assertThat(feed.first().isMyPost).isEqualTo(true)
+        assertThat(feed.last().isMyPost).isEqualTo(false)
+    }
+
+    @Test
+    fun `findFeed should throw when not signed in`() {
+        assertFailsWith<AccessDeniedException> {
+            feedService.findFeed(PageParams())
+        }
+    }
+
+}
diff --git a/src/test/kotlin/com/myapp/service/MicropostServiceTest.kt b/src/test/kotlin/com/myapp/service/MicropostServiceTest.kt
new file mode 100644
index 0000000..2610136
--- /dev/null
+++ b/src/test/kotlin/com/myapp/service/MicropostServiceTest.kt
@@ -0,0 +1,144 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.Micropost
+import com.myapp.dto.request.PageParams
+import com.myapp.repository.MicropostRepository
+import com.myapp.service.exception.NotAuthorizedException
+import com.myapp.testing.TestMicropost
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.argumentCaptor
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import com.nhaarman.mockito_kotlin.verify
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.security.access.AccessDeniedException
+import kotlin.test.assertFailsWith
+
+class MicropostServiceTest {
+
+    private val micropostRepository: MicropostRepository = mock()
+    private val securityContextService: SecurityContextService = mock()
+    private val micropostService by lazy {
+        MicropostServiceImpl(
+            micropostRepository = micropostRepository,
+            securityContextService = securityContextService
+        )
+    }
+
+    @Test
+    fun `findAllByUser should find posts by user when signed in and the posts belongs to current user`() {
+        `when`(securityContextService.currentUser())
+            .doReturn(TestUser.copy(_id = 1))
+        `when`(micropostRepository.findAllByUser(1, PageParams()))
+            .doReturn(listOf(TestMicropost))
+
+        micropostService.findAllByUser(1, PageParams()).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().isMyPost).isEqualTo(true)
+        }
+    }
+
+    @Test
+    fun `findAllByUser should find posts by user when signed in and the posts belongs to another user`() {
+        `when`(securityContextService.currentUser())
+            .doReturn(TestUser.copy(_id = 1))
+        `when`(micropostRepository.findAllByUser(2, PageParams()))
+            .doReturn(listOf(TestMicropost))
+
+        micropostService.findAllByUser(2, PageParams()).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().isMyPost).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun `findAllByUser should find posts by user when not signed`() {
+        `when`(micropostRepository.findAllByUser(1, PageParams()))
+            .doReturn(listOf(TestMicropost))
+
+        micropostService.findAllByUser(1, PageParams()).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().isMyPost).isNull()
+        }
+    }
+
+    @Test
+    fun `findMyPosts should find my posts`() {
+        `when`(securityContextService.currentUser())
+            .doReturn(TestUser.copy(_id = 1))
+        `when`(micropostRepository.findAllByUser(1, PageParams()))
+            .doReturn(listOf(TestMicropost))
+
+        micropostService.findMyPosts(PageParams()).apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(first().isMyPost).isEqualTo(true)
+        }
+    }
+
+    @Test
+    fun `findMyPosts should throw when not signed in`() {
+        assertFailsWith<AccessDeniedException> {
+            micropostService.findMyPosts(PageParams())
+        }
+    }
+
+    @Test
+    fun `create should create a post`() {
+        `when`(securityContextService.currentUser()).doReturn(TestUser)
+
+        micropostService.create("my test content")
+
+        argumentCaptor<Micropost>().apply {
+            verify(micropostRepository).create(capture())
+            assertThat(firstValue.content).isEqualTo("my test content")
+            assertThat(firstValue.user).isEqualTo(TestUser)
+        }
+    }
+
+    @Test
+    fun `create should throw when not signed in`() {
+        assertFailsWith<AccessDeniedException> {
+            micropostService.create("my test content")
+        }
+    }
+
+    @Test
+    fun `delete should delete a post`() {
+        val currentUser = TestUser.copy(_id = 1)
+        `when`(securityContextService.currentUser())
+            .doReturn(TestUser.copy(_id = 1))
+        `when`(micropostRepository.findOne(101))
+            .doReturn(TestMicropost.copy(
+                user = currentUser
+            ))
+
+        micropostService.delete(101)
+
+        verify(micropostRepository).delete(101)
+    }
+
+    @Test
+    fun `delete should not delete a post when the post does not belong to current user`() {
+        `when`(securityContextService.currentUser())
+            .doReturn(TestUser.copy(_id = 1))
+        `when`(micropostRepository.findOne(101))
+            .doReturn(TestMicropost.copy(
+                user = TestUser.copy(_id = 2)
+            ))
+
+        assertFailsWith<NotAuthorizedException> {
+            micropostService.delete(101)
+        }
+    }
+
+    @Test
+    fun `delete should throw when not signed in`() {
+        assertFailsWith<AccessDeniedException> {
+            micropostService.delete(1)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/service/RelatedUserServiceTest.kt b/src/test/kotlin/com/myapp/service/RelatedUserServiceTest.kt
new file mode 100644
index 0000000..eb8ff66
--- /dev/null
+++ b/src/test/kotlin/com/myapp/service/RelatedUserServiceTest.kt
@@ -0,0 +1,105 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.Relationship
+import com.myapp.dto.request.PageParams
+import com.myapp.repository.RelatedUserRepository
+import com.myapp.repository.RelationshipRepository
+import com.myapp.testing.TestRelatedUser
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+
+class RelatedUserServiceTest {
+
+    private val relatedUserRepository: RelatedUserRepository = mock()
+    private val relationshipRepository: RelationshipRepository = mock()
+    private val securityContextService: SecurityContextService = mock()
+    private val relatedUserService by lazy {
+        RelatedUserServiceImpl(
+            relatedUserRepository = relatedUserRepository,
+            relationshipRepository = relationshipRepository,
+            securityContextService = securityContextService
+        )
+    }
+
+    @Test
+    fun `findFollowers should find followers`() {
+        val user = TestUser.copy(_id = 1)
+        val currentUser = TestUser.copy(_id = 2)
+        val followers = listOf(
+            TestRelatedUser.copy(_id = 101),
+            TestRelatedUser.copy(_id = 102)
+        )
+        val currentUserRelationships = listOf(
+            Relationship(follower = currentUser, followed = TestUser.copy(_id = 101))
+        )
+        `when`(securityContextService.currentUser())
+            .doReturn(currentUser)
+        `when`(relatedUserRepository.findFollowers(user.id, PageParams()))
+            .doReturn(followers)
+        `when`(relationshipRepository.findAllByFollowerAndFollowedUsers(currentUser.id, followers.map { it.id }))
+            .doReturn(currentUserRelationships)
+
+        val foundFollowers = relatedUserService.findFollowers(user.id, PageParams())
+
+        assertThat(foundFollowers.size).isEqualTo(2)
+        assertThat(foundFollowers.first().isFollowedByMe).isEqualTo(true)
+        assertThat(foundFollowers.last().isFollowedByMe).isEqualTo(false)
+    }
+
+    @Test
+    fun `findFollowers should find followers when not signed in`() {
+        val user = TestUser.copy(_id = 1)
+        val followers = listOf(TestRelatedUser.copy(_id = 101))
+        `when`(relatedUserRepository.findFollowers(user.id, PageParams()))
+            .doReturn(followers)
+
+        val foundFollowers = relatedUserService.findFollowers(user.id, PageParams())
+
+        assertThat(foundFollowers.size).isEqualTo(1)
+        assertThat(foundFollowers.first().isFollowedByMe).isNull()
+    }
+
+    @Test
+    fun `findFollowings should find followings`() {
+        val user = TestUser.copy(_id = 1)
+        val currentUser = TestUser.copy(_id = 2)
+        val followings = listOf(
+            TestRelatedUser.copy(_id = 101),
+            TestRelatedUser.copy(_id = 102)
+        )
+        val currentUserRelationships = listOf(
+            Relationship(follower = currentUser, followed = TestUser.copy(_id = 101))
+        )
+        `when`(securityContextService.currentUser())
+            .doReturn(currentUser)
+        `when`(relatedUserRepository.findFollowings(user.id, PageParams()))
+            .doReturn(followings)
+        `when`(relationshipRepository.findAllByFollowerAndFollowedUsers(currentUser.id, followings.map { it.id }))
+            .doReturn(currentUserRelationships)
+
+        val foundFollowings = relatedUserService.findFollowings(user.id, PageParams())
+
+        assertThat(foundFollowings.size).isEqualTo(2)
+        assertThat(foundFollowings.first().isFollowedByMe).isEqualTo(true)
+        assertThat(foundFollowings.last().isFollowedByMe).isEqualTo(false)
+    }
+
+    @Test
+    fun `findFollowings should find followings when not signed in`() {
+        val user = TestUser.copy(_id = 1)
+        val followings = listOf(TestRelatedUser.copy(_id = 101))
+        `when`(relatedUserRepository.findFollowings(user.id, PageParams()))
+            .doReturn(followings)
+
+        val foundFollowings = relatedUserService.findFollowings(user.id, PageParams())
+
+        assertThat(foundFollowings.size).isEqualTo(1)
+        assertThat(foundFollowings.first().isFollowedByMe).isNull()
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/service/RelationshipServiceTest.kt b/src/test/kotlin/com/myapp/service/RelationshipServiceTest.kt
new file mode 100644
index 0000000..6e11352
--- /dev/null
+++ b/src/test/kotlin/com/myapp/service/RelationshipServiceTest.kt
@@ -0,0 +1,70 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.Relationship
+import com.myapp.repository.RelationshipRepository
+import com.myapp.repository.UserRepository
+import com.myapp.service.exception.RelationshipNotFoundException
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.*
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import kotlin.test.assertFailsWith
+
+class RelationshipServiceTest {
+
+    private val userRepository: UserRepository = mock()
+    private val relationshipRepository: RelationshipRepository = mock()
+    private val securityContextService: SecurityContextService = mock()
+    private val relationshipService by lazy {
+        RelationshipServiceImpl(
+            userRepository = userRepository,
+            relationshipRepository = relationshipRepository,
+            securityContextService = securityContextService
+        )
+    }
+
+    @Test
+    fun `follow should follow user`() {
+        val user = TestUser.copy()
+        val currentUser = TestUser.copy()
+        `when`(userRepository.findOne(1)).doReturn(user)
+        `when`(securityContextService.currentUser()).doReturn(currentUser)
+
+        relationshipService.follow(1)
+
+        argumentCaptor<Relationship>().apply {
+            verify(relationshipRepository).create(capture())
+            assertThat(firstValue.follower).isEqualTo(currentUser)
+            assertThat(firstValue.followed).isEqualTo(user)
+        }
+    }
+
+    @Test
+    fun `unfollow should unfollow user`() {
+        val currentUser = TestUser.copy()
+        `when`(securityContextService.currentUser()).doReturn(currentUser)
+        `when`(relationshipRepository.findOneByFollowerAndFollowed(currentUser.id, 1))
+            .doReturn(Relationship(
+                _id = 101,
+                follower = currentUser,
+                followed = TestUser.copy()
+            ))
+
+        relationshipService.unfollow(1)
+
+        verify(relationshipRepository).delete(101)
+    }
+
+    @Test
+    fun `unfollow should not unfollow user when no relationship`() {
+        val currentUser = TestUser.copy()
+        `when`(securityContextService.currentUser()).doReturn(currentUser)
+
+        assertFailsWith<RelationshipNotFoundException> {
+            relationshipService.unfollow(1)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/service/UserDetailsServiceTest.kt b/src/test/kotlin/com/myapp/service/UserDetailsServiceTest.kt
new file mode 100644
index 0000000..384119b
--- /dev/null
+++ b/src/test/kotlin/com/myapp/service/UserDetailsServiceTest.kt
@@ -0,0 +1,40 @@
+package com.myapp.service
+
+import com.myapp.repository.UserRepository
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+import kotlin.test.assertFailsWith
+
+class UserDetailsServiceTest {
+
+    private val userRepository: UserRepository = mock()
+    private val userDetailsService by lazy {
+        UserDetailsServiceImpl(
+            userRepository = userRepository
+        )
+    }
+
+    @Test
+    fun `loadUserByUsername should load user by username`() {
+        `when`(userRepository.findOneByUsername("test1@test.com"))
+            .doReturn(TestUser)
+
+        val userDetails = userDetailsService.loadUserByUsername("test1@test.com")
+
+        assertThat(userDetails).isNotNull()
+        assertThat(userDetails.user).isEqualTo(TestUser)
+    }
+
+    @Test
+    fun `loadUserByUsername should throw when username was not found`() {
+        assertFailsWith<UsernameNotFoundException> {
+            userDetailsService.loadUserByUsername("test1@test.com")
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/service/UserServiceTest.kt b/src/test/kotlin/com/myapp/service/UserServiceTest.kt
new file mode 100644
index 0000000..e66bee4
--- /dev/null
+++ b/src/test/kotlin/com/myapp/service/UserServiceTest.kt
@@ -0,0 +1,176 @@
+package com.myapp.service
+
+import com.myapp.auth.SecurityContextService
+import com.myapp.domain.Relationship
+import com.myapp.domain.User
+import com.myapp.dto.page.PageImpl
+import com.myapp.dto.request.UserEditParams
+import com.myapp.dto.request.UserNewParams
+import com.myapp.repository.RelationshipRepository
+import com.myapp.repository.UserRepository
+import com.myapp.testing.TestUser
+import com.nhaarman.mockito_kotlin.argumentCaptor
+import com.nhaarman.mockito_kotlin.doReturn
+import com.nhaarman.mockito_kotlin.mock
+import com.nhaarman.mockito_kotlin.verify
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.springframework.security.access.AccessDeniedException
+import kotlin.test.assertFailsWith
+
+
+class UserServiceTest {
+
+    private val userRepository: UserRepository = mock()
+    private val relationshipRepository: RelationshipRepository = mock()
+    private val securityContextService: SecurityContextService = mock()
+    private val userService by lazy {
+        UserServiceImpl(
+            userRepository = userRepository,
+            relationshipRepository = relationshipRepository,
+            securityContextService = securityContextService
+        )
+    }
+
+    @Test
+    fun `findOne should find a user who is followed by current user`() {
+        val currentUser = TestUser.copy(_id = 1)
+        val user = TestUser.copy(_id = 2)
+        `when`(securityContextService.currentUser())
+            .doReturn(currentUser)
+        `when`(userRepository.findOneWithStats(2))
+            .doReturn(user)
+        `when`(relationshipRepository.findOneByFollowerAndFollowed(1, 2))
+            .doReturn(Relationship(
+                follower = currentUser,
+                followed = user
+            ))
+
+        userService.findOne(2).apply {
+            assertThat(id).isEqualTo(2)
+            assertThat(isFollowedByMe).isEqualTo(true)
+            assertThat(isMyself).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun `findOne should find a user who is current user itself`() {
+        val currentUser = TestUser.copy(_id = 1)
+        val user = currentUser
+        `when`(securityContextService.currentUser())
+            .doReturn(currentUser)
+        `when`(userRepository.findOneWithStats(1))
+            .doReturn(user)
+
+        userService.findOne(1).apply {
+            assertThat(id).isEqualTo(1)
+            assertThat(isMyself).isEqualTo(true)
+            assertThat(isFollowedByMe).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun `findOne should find a user when not signed in`() {
+        `when`(userRepository.findOneWithStats(1))
+            .doReturn(TestUser.copy(_id = 1))
+
+        userService.findOne(1).apply {
+            assertThat(id).isEqualTo(1)
+            assertThat(isMyself).isNull()
+            assertThat(isFollowedByMe).isNull()
+        }
+    }
+
+    @Test
+    fun `findMe should find me`() {
+        val currentUser = TestUser.copy(_id = 1)
+        val user = currentUser
+        `when`(securityContextService.currentUser())
+            .doReturn(currentUser)
+        `when`(userRepository.findOneWithStats(1))
+            .doReturn(user)
+
+        userService.findMe().apply {
+            assertThat(id).isEqualTo(1)
+            assertThat(isMyself).isEqualTo(true)
+            assertThat(isFollowedByMe).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun `findMe should throw when not signed in`() {
+        assertFailsWith<AccessDeniedException> {
+            userService.findMe()
+        }
+    }
+
+    @Test
+    fun `findAll should find users`() {
+        val page = PageImpl(
+            content = listOf(TestUser),
+            totalPages = 1
+        )
+        `when`(userRepository.findAll(1, 1)).doReturn(page)
+
+        userService.findAll(1, 1).apply {
+            assertThat(this).isEqualTo(page)
+        }
+    }
+
+    @Test
+    fun `create should create a user`() {
+        userService.create(UserNewParams(
+            email = "test1@test.com",
+            password = "secret123",
+            name = "John Doe"
+        ))
+
+        argumentCaptor<User>().apply {
+            verify(userRepository).create(capture())
+            assertThat(firstValue.username).isEqualTo("test1@test.com")
+            assertThat(firstValue.password).matches("^\\$2[ayb]\\$.{56}$")
+            assertThat(firstValue.name).isEqualTo("John Doe")
+        }
+    }
+
+    @Test
+    fun `updateMe should update me`() {
+        `when`(securityContextService.currentUser()).doReturn(TestUser)
+
+        userService.updateMe(UserEditParams(
+            email = "test1@test.com",
+            password = "secret123",
+            name = "John Doe"
+        ))
+
+        argumentCaptor<User>().apply {
+            verify(userRepository).update(capture())
+            assertThat(firstValue.username).isEqualTo("test1@test.com")
+            assertThat(firstValue.password).matches("^\\$2[ayb]\\$.{56}$")
+            assertThat(firstValue.name).isEqualTo("John Doe")
+        }
+    }
+
+    @Test
+    fun `updateMe should update me with blank parameters`() {
+        `when`(securityContextService.currentUser()).doReturn(TestUser)
+
+        userService.updateMe(UserEditParams())
+
+        argumentCaptor<User>().apply {
+            verify(userRepository).update(capture())
+            assertThat(firstValue.username).isEqualTo(TestUser.username)
+            assertThat(firstValue.password).isEqualTo(TestUser.password)
+            assertThat(firstValue.name).isEqualTo(TestUser.name)
+        }
+    }
+
+    @Test
+    fun `updateMe should throw when not signed in`() {
+        assertFailsWith<AccessDeniedException> {
+            userService.updateMe(UserEditParams())
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/myapp/testing/TestUtil.kt b/src/test/kotlin/com/myapp/testing/TestUtil.kt
new file mode 100644
index 0000000..5a7f332
--- /dev/null
+++ b/src/test/kotlin/com/myapp/testing/TestUtil.kt
@@ -0,0 +1,24 @@
+package com.myapp.testing
+
+import com.myapp.domain.Micropost
+import com.myapp.domain.RelatedUser
+import com.myapp.domain.User
+
+val TestUser = User(
+    _id = 1,
+    username = "test@test.com",
+    password = "encrypted password",
+    name = "John Doe"
+)
+val TestMicropost = Micropost(
+    _id = 1,
+    content = "test content",
+    user = TestUser
+)
+val TestRelatedUser = RelatedUser(
+    _id = 1,
+    username = "test@test.com",
+    name = "John Doe",
+    relationshipId = 1
+)
+
