Java is one of the popular and secure programming language for server side application development. Most of the companies specially Big enterprises like bank, financial institutions and others. If you compare the application developed in all languages. Java is on top with 65%
Developers also like to work in Java and Spring due to huge job market in the world. Average salary of Java developers is more than other language developers.
Rest API : Now every application have mainly two parts like API part and UI part. API can be developed using any server side language. Java spring boot is very popular these days. Spring boot is written on the top of the Java Spring and hibernate.
What we are going to create
Here we are going to create a demo Rest API usng the Java Spring boot, JPA, JSON WEB Token (JWT) and database mysql. JWT is a token based authentication mechanism (OAuth 2) used to register and login securely.
News CRUD APIs : Deails of the APIs below.
- Signup the news using the username and password.
- Signin using the username and password.
- Create new Post.
- Get all Post Details.
- Get the Post on basis of post Id.
- Update Post.
- Create Comment
- Update Comment.
- Get all Comments.
- Get single comment.
Prerequisites
- Java – 1.8.x
- Spring Boot
- JPA
- Maven – 3.x.x
- Mysql – 5.x.x
- JWT
- IntelliJ IDEA IDEA (optional, you can choose any)
- Postman
Watch Demo of working app
Bootstrap the SpringBoot project using Spring Initializr https://start.spring.io/ and create the project structure like below
/pom.xml :
Add application dependencies, plugins, etc. These are dowloaded automatically, no need to download manually.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ved</groupId>
<artifactId>news</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>news</name>
<description>Rest API for a Simple News App</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<defaultGoal>install</defaultGoal>
</build>
</project>
src/main/resources/application.properties
Application properties are added in this file, there would be multiple file production, development and test environment. like application-dev.properties, application-prod.properties, application-tests.properties etc.
Add all the details in your application.properties file (default)
spring.profiles.active=dev
spring.application.name=Profiles
app.message= This is the property file for the ${spring.application.name}
## Server Properties
#server.port= 8080
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/api_news?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
spring.datasource.username = root
spring.datasource.password =
## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
# logging
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
# Show or not log for each sql query
spring.jpa.show-sql = true
## App Properties
app.jwtSecret= secretmysecret
app.jwtExpirationInMs = 604800000
Here I have set the default profile to dev. database is mysql with username root and password blank. Other properties related to JPA and JWT etc. Web development company noida
Response : Define response type
src/main/java/responses/ApiResponse.java
package responses;
public class ApiResponse {
private Boolean success;
private String message;
public ApiResponse(Boolean success, String message) {
this.success = success;
this.message = message;
}
public ApiResponse() {}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
src/main/java/responses/JwtAuthenticationResponse.java
package responses;
public class JwtAuthenticationResponse {
private String accessToken;
private String tokenType = "Bearer"; // default
public JwtAuthenticationResponse(String accessToken) {
this.accessToken = accessToken;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
}
src/main/java/responses/LoginRequest.java
package responses;
import javax.validation.constraints.NotBlank;
public class LoginRequest {
@NotBlank
private String usernameOrEmail;
@NotBlank
private String password;
public String getUsernameOrEmail() {
return usernameOrEmail;
}
public void setUsernameOrEmail(String usernameOrEmail) {
this.usernameOrEmail = usernameOrEmail;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
src/main/java/responses/SignUpRequest.java
package responses;
import com.ved.news.model.Role;
import com.ved.news.model.UserContact;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
public class SignUpRequest {
@NotBlank
@Size(min = 3, max = 50)
private String name;
@NotBlank
@Size( min = 3, max = 20)
private String username;
@NotBlank
@Email
@Size(max = 40)
private String email;
@NotBlank
@Size(min = 5, max = 20)
private String password;
private Set<Role> roles = new HashSet<>();
private UserContact userContact;
public UserContact getUserContact() {
return userContact;
}
public void setUserContact(UserContact userContact) {
this.userContact = userContact;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
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;
}
}
Config
SecurityConfig.java
package com.ved.news.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.ved.news.security.UserDetailsServiceImpl;
import com.ved.news.security.JwtAuthenticationEntryPoint;
import com.ved.news.security.JwtAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserDetailsServiceImpl userDetailsServiceImpl;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsServiceImpl)
.passwordEncoder(passwordEncoder());
}
@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.anyRequest()
.authenticated();
// Add our custom JWT security filter
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
WebMvcConfig.java
package com.ved.news.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final long MAX_AGE_SECS = 3600; // 1 hr
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.maxAge(MAX_AGE_SECS);
}
}
Controller
AuthenticationController.java added for user signup and signin.
package com.ved.news.controller;
import java.net.URI;
import java.util.Collections;
import javax.validation.Valid;
import com.ved.news.model.UserContact;
import com.ved.news.repository.RoleRepository;
import com.ved.news.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
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;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.ved.news.exception.AppException;
import com.ved.news.model.Role;
import com.ved.news.model.RoleName;
import com.ved.news.model.User;
import com.ved.news.security.JwtTokenProvider;
import responses.ApiResponse;
import responses.JwtAuthenticationResponse;
import responses.LoginRequest;
import responses.SignUpRequest;
@RestController
@RequestMapping("/api/auth")
public class AuthenticationController {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
JwtTokenProvider tokenProvider;
@PostMapping("/signin")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
@PostMapping("/signup")
public ResponseEntity<?> registerUser(@Valid @RequestBody SignUpRequest signUpRequest) {
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return new ResponseEntity(new ApiResponse(false, "Username is already taken!"), HttpStatus.BAD_REQUEST);
}
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return new ResponseEntity(new ApiResponse(false, "Email Address already in use!"), HttpStatus.BAD_REQUEST);
}
// Creating user's account
User user = new User();
user.setName(signUpRequest.getName());
user.setUsername(signUpRequest.getUsername());
user.setEmail(signUpRequest.getEmail());
user.setPassword(passwordEncoder.encode(signUpRequest.getPassword()));
user.setUserContact(signUpRequest.getUserContact());
if( signUpRequest.getUserContact() != null) {
UserContact usrContact = new UserContact();
usrContact.setPhoneNo(signUpRequest.getUserContact().getPhoneNo());
usrContact.setUser(user);
}
if(signUpRequest.getRoles().size() > 0) {
user.setRoles(signUpRequest.getRoles());
} else {
Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
.orElseThrow(() -> new AppException("User Role not set."));
user.setRoles(Collections.singleton(userRole));
}
User result = userRepository.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/users/{username}")
.buildAndExpand(result.getUsername()).toUri();
return ResponseEntity.created(location).body(new ApiResponse(true, "User registered successfully"));
}
}
PostController.java add new news, update news, delete news.
package com.ved.news.controller;
import com.ved.news.exception.ResourceNotFoundException;
import com.ved.news.model.Category;
import com.ved.news.model.Post;
import com.ved.news.repository.CategoryRepository;
import com.ved.news.repository.CommentRepository;
import com.ved.news.repository.PostRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.Param;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Optional;
@Slf4j
@RestController
@RequestMapping("/api")
public class PostController {
@Autowired
PostRepository postRepository;
@Autowired
CategoryRepository categoryRepository;
@Autowired
CommentRepository commentRepository;
@GetMapping("/posts")
@PreAuthorize("hasRole('USER')")
public Page<Post> getAllPosts(Pageable pageable) {
log.debug("in get post controller");
return postRepository.findAll(pageable);
}
@PostMapping("/posts")
@PreAuthorize("hasRole('USER')")
public Post createPost(@Valid @RequestBody Post postDetail) {
log.info("in POST post controller");
return postRepository.save(postDetail);
}
@PutMapping("/posts/{postId}")
@PreAuthorize("hasRole('USER')")
public Post updatePost(@PathVariable Long postId, @Valid @RequestBody Post postRequest) {
return postRepository.findById(postId).map(post -> {
post.setTitle(postRequest.getTitle());
post.setContent(postRequest.getContent());
post.setDescription(postRequest.getDescription());
return postRepository.save(post);
}).orElseThrow(() -> new ResourceNotFoundException("Post", "postId", postId));
}
@GetMapping("/posts/{id}")
public Object getPostById(@PathVariable(value = "id") Long id) {
return postRepository.findById(id);
}
@DeleteMapping("/posts/{postId}")
public ResponseEntity<?> deletePost(@PathVariable Long postId) {
return postRepository.findById(postId).map(post -> {
postRepository.delete(post);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("Post", "postId", postId));
}
@GetMapping("categories")
public List<Category> getCategory() {
return categoryRepository.findAll();
}
@GetMapping("categories/{catId}")
public Optional<Category> getCategoryById(@PathVariable Integer catId) {
return categoryRepository.findById(catId);
}
@GetMapping("categoriesByName/{name}")
public Optional<Category> getCategoryByName(@PathVariable String name) {
return categoryRepository.findByName(name);
}
}
CommentController.java used to add new comment, delete and update comment.
package com.ved.news.controller;
import com.ved.news.exception.ResourceNotFoundException;
import com.ved.news.model.Comment;
import com.ved.news.repository.CommentRepository;
import com.ved.news.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api")
public class CommentController {
@Autowired
CommentRepository commentRepository;
@Autowired
PostRepository postRepository;
@GetMapping("/posts/{postId}/comments")
public Page<Comment> getAllCommentsByPostId(@PathVariable(value = "postId") Long postId,
Pageable pageable) {
return commentRepository.findByPostId(postId, pageable);
}
@PostMapping("/posts/{postId}/comments")
public Comment createComment(@PathVariable (value = "postId") Long postId,
@Valid @RequestBody Comment comment) {
return postRepository.findById(postId).map(post -> {
comment.setPost(post);
return commentRepository.save(comment);
}).orElseThrow(() -> new ResourceNotFoundException("Comment", "PostId", postId));
}
@PutMapping("/posts/{postId}/comments/{commentId}")
public Comment updateComment(@PathVariable (value = "postId") Long postId,
@PathVariable (value = "commentId") Long commentId,
@Valid @RequestBody Comment commentRequest) {
if(!postRepository.existsById(postId)) {
throw new ResourceNotFoundException("Comment", "PostId", postId);
}
return commentRepository.findById(commentId).map(comment -> {
comment.setText(commentRequest.getText());
return commentRepository.save(comment);
}).orElseThrow(() -> new ResourceNotFoundException("Comment", "commentId", commentId));
}
@DeleteMapping("/posts/{postId}/comments/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable (value = "postId") Long postId,
@PathVariable (value = "commentId") Long commentId) {
if(!postRepository.existsById(postId)) {
throw new ResourceNotFoundException("Comment", "PostId", postId);
}
return commentRepository.findById(commentId).map(comment -> {
commentRepository.delete(comment);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("Comment", "commentId", commentId));
}
}
Exception:
used to handle error during run time.
AppException.java
/*
* @author Ved Prakash Maurya
*/
package com.ved.news.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class AppException extends RuntimeException {
public AppException(String message) {
super(message);
}
public AppException(String message, Throwable cause) {
super(message, cause);
}
}
BadRequestException.java
package com.ved.news.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
public BadRequestException(String message, Throwable cause) {
super(message, cause);
}
}
com/ved/news/NewsApplication.java
package com.ved.news;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class NewsApplication {
public static void main(String[] args){
SpringApplication.run(NewsApplication.class, args);
}
}
Code is available on github
Thanks for reading. comment in comment box for anything related to code.