Spring Boot + Spring Security + JWT + MySQL + React Full Stack Polling App - Part 1
Spring BootFebruary 05, 20184 mins readHello and Welcome to the first part of an exciting series of blog posts where you will learn how to build an end-to-end full stack polling app similar to twitter polls.
We’ll build the backend server using Spring Boot where we’ll use Spring Security along with JWT authentication. We’ll use MySQL database for storage.
The front-end application will be built using React. We’ll also use Ant Design for designing our user interface.
In the end of this tutorial series, you’ll have built a fully-fledged polling application from scratch like a boss.
The complete source code of the project is hosted on Github. You can refer that anytime if you get stuck at something.
Following is the screenshot of the final version of our application -
Looks great, isn’t it? Well, then let’s start building it from scratch…
In this article, We’ll set up the backend project using Spring Boot and define the basic domain models and repositories.
Creating the Backend Application using Spring Boot
Let’s bootstrap the project using Spring Initialzr web tool -
- Open http://start.spring.io
- Enter polls in Artifact field.
- Add Web, JPA, MySQL, Validation and Security dependencies from the Dependencies section.
- Click Generate to generate and download the project.
Once the project is downloaded, unzip it and import it into your favorite IDE. The directory structure of the project will look like this-
Adding additional dependencies
We’ll need to add few additional dependencies to our project. Open pom.xml
file from the root directory of your generated project and add the following to the <dependencies>
section -
<!-- For Working with Json Web Tokens (JWT) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<!-- For Java 8 Date/Time Support -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Configuring the Server, Database, Hibernate and Jackson
Let’s now configure the server, database, hibernate, and jackson by adding the following properties to the src/main/resources/application.properties
file -
## Server Properties
server.port= 5000
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url= jdbc:mysql://localhost:3306/polling_app?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username= root
spring.datasource.password= callicoder
## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update
## Hibernate Logging
logging.level.org.hibernate.SQL= DEBUG
# Initialize the datasource with available DDL and DML scripts
spring.datasource.initialization-mode=always
## Jackson Properties
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS= false
spring.jackson.time-zone= UTC
All the above properties are self-explanatory. I’ve set hibernate’s ddl-auto
property to update
. This will automatically create/update the tables in the database according to the entities in our application.
The Jackson’s WRITE_DATES_AS_TIMESTAMPS
property is used to disable serializing Java 8 Data/Time values as timestamps. All the Date/Time values will be serialized to ISO date/time string.
Before proceeding further, please create a database named polling_app
in MySQL and change the spring.datasource.username
and spring.datasource.password
properties as per your MySQL installation.
Configuring Spring Boot to use Java 8 Date/Time converters and UTC Timezone
We’ll be using Java 8 Data/Time classes in our domain models. We’ll need to register JPA 2.1 converters so that all the Java 8 Date/Time fields in the domain models automatically get converted to SQL types when we persist them in the database.
Moreover, We’ll set the default timezone for our application to UTC.
Open the main class PollsApplication.java
and make the following modifications to it-
package com.example.polls;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
import javax.annotation.PostConstruct;
import java.util.TimeZone;
@SpringBootApplication
@EntityScan(basePackageClasses = {
PollsApplication.class,
Jsr310JpaConverters.class
})
public class PollsApplication {
@PostConstruct
void init() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
public static void main(String[] args) {
SpringApplication.run(PollsApplication.class, args);
}
}
Creating the domain models
Our application will allow new users to register and login to our application. Every User will have one or more roles. The roles associated with a user will be used in future to decide whether the user is authorized to access a particular resource on our server or not.
In this section, We’ll create the User
and Role
domain models. All the domain models will be stored in a package named model
inside com.example.polls
.
User
model
1. The User
model contains the following fields -
id
: Primary Keyusername
: A unique usernameemail
: A unique emailpassword
: A password which will be stored in encrypted format.roles
: A set of roles. (Many-To-Many relationship withRole
entity)
Here is the complete User
class -
package com.example.polls.model;
import com.example.polls.model.audit.DateAudit;
import org.hibernate.annotations.NaturalId;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users", uniqueConstraints = {
@UniqueConstraint(columnNames = {
"username"
}),
@UniqueConstraint(columnNames = {
"email"
})
})
public class User extends DateAudit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Size(max = 40)
private String name;
@NotBlank
@Size(max = 15)
private String username;
@NaturalId
@NotBlank
@Size(max = 40)
@Email
private String email;
@NotBlank
@Size(max = 100)
private String password;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public User() {
}
public User(String name, String username, String email, String password) {
this.name = name;
this.username = username;
this.email = email;
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
The User
class extends the DateAudit
class that we’ll define shortly. The DateAudit
class will have createdAt
and updatedAt
fields that will be used for auditing purposes.
Role
model
2. The Role
class contains an id
and a name
field. The name
field is an enum
. We’ll have a fixed set of pre-defined roles. So it makes sense to make the role name as enum
.
Here is the complete code for Role
class -
package com.example.polls.model;
import org.hibernate.annotations.NaturalId;
import javax.persistence.*;
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
@NaturalId
@Column(length = 60)
private RoleName name;
public Role() {
}
public Role(RoleName name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public RoleName getName() {
return name;
}
public void setName(RoleName name) {
this.name = name;
}
}
RoleName enum
Following is the RoleName
enum -
package com.example.polls.model;
public enum RoleName {
ROLE_USER,
ROLE_ADMIN
}
I have defined two roles namely ROLE_USER
and ROLE_ADMIN
. You’re free to add more roles as per your project requirements.
DateAudit
model
3. All right! Let’s now define the DateAudit
model. It will have a createdAt
and an updatedAt
field. Other domain models that need these auditing fields will simply extend this class.
We’ll use JPA’s AuditingEntityListener
to automatically populate createdAt
and updatedAt
values when we persist an entity.
Here is the Complete DateAudit
class (I’ve created a package named audit
inside com.example.polls.model
package to store all the auditing related models) -
package com.example.polls.model.audit;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.time.Instant;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
value = {"createdAt", "updatedAt"},
allowGetters = true
)
public abstract class DateAudit implements Serializable {
@CreatedDate
@Column(nullable = false, updatable = false)
private Instant createdAt;
@LastModifiedDate
@Column(nullable = false)
private Instant updatedAt;
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
}
To enable JPA Auditing, we’ll need to add @EnableJpaAuditing
annotation to our main class or any other configuration classes.
Let’s create an AuditingConfig
configuration class and add the @EnableJpaAuditing
annotation to it.
We’re creating a separate class because we’ll be adding more auditing related configurations later. So it’s better to have a separate class.
We’ll keep all the configuration classes inside a package named config
. Go ahead and create the config
package inside com.example.polls
, and then create the AuditingConfig
class inside config
package -
package com.example.polls.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration
@EnableJpaAuditing
public class AuditingConfig {
// That's all here for now. We'll add more auditing configurations later.
}
User
and Role
data
Creating the Repositories for accessing Now that we have defined the domain models, Let’s create the repositories for persisting these domain models to the database and retrieving them.
All the repositories will go inside a package named repository
. So let’s first create the repository
package inside com.example.polls
.
UserRepository
1. Following is the complete code for UserRepository
interface. It extends Spring Data JPA’s JpaRepository
interface.
package com.example.polls.repository;
import com.example.polls.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByUsernameOrEmail(String username, String email);
List<User> findByIdIn(List<Long> userIds);
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
RoleRepository
2. Following is the RoleRepository
interface. It contains a single method to retrieve a Role
from the RoleName
-
package com.example.polls.repository;
import com.example.polls.model.Role;
import com.example.polls.model.RoleName;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(RoleName roleName);
}
Exploring the current setup and Running the Application
After creating all the above models, repositories and configurations, our current project should look like this -
You can run the application by typing the following command from the root directory of your project -
mvn spring-boot:run
Check out the logs and make sure that the server starts successfully.
2018-02-24 22:40:44.998 INFO 33708 --- Tomcat started on port(s): 5000 (http)
2018-02-24 22:40:45.008 INFO 33708 --- Started PollsApplication in 7.804 seconds (JVM running for 27.193)
Write to me in the comment section, if the server doesn’t start successfully for you. I’ll help you out.
Creating Default Roles
We’ll have a fixed set of predefined roles in our application. Whenever a user logs in, we’ll assign ROLE_USER
to it by default.
For assigning the roles, they have to be present in the database. So let’s create the two default roles in the database by executing the following insert statements -
INSERT INTO roles(name) VALUES('ROLE_USER');
INSERT INTO roles(name) VALUES('ROLE_ADMIN');
What’s Next?
In the next chapter of this series, we’ll learn how to configure Spring Security in our project and add functionalities to register a new user and log them in.
Read Next: Full Stack Polling App with Spring Boot, Spring Security, JWT, MySQL and React - Part 2