Angular 7 CRUD Example (Full Stack App)

Angular 7 CRUD Example (Full Stack App) thumbnail
180K
By Dhiraj 18 April, 2018

In this article, we will be creating a sample Angular 7 application step by step from scratch and perform CRUD operations on a user entity. The back-end API will be exposed using Spring Boot REST and MySQL will be used for persistent storage. We will be using Angular CLI to generate our sample project and then we will add our all user components to it. We will have a login page with token-based authentication. We will be using routing configuration to switch in between the routes and ReactiveFormsModule for creating forms. We will also be implementing HttpInterceptor at client side to intercept all the API request to add auth token in the header. Hence, in short, we will be creating a full stack app using angular 7 and spring boot.

You can follow this article for Angular 8 integration with Spring Boot.

Angular 8 just got released this May and here is the article for Angular 8 CRUD example.

Angular 7 Release Highlights

Angular 7 got released this year in October with multiple new features such as CLI Prompts, Virtual Scroll, Drag and Drop, Angular Budgets and many more. From this release, Angular supports Node 10 and simultaneously maintains Node 8 support with TypeScript 3.1 and RxJS 6.3. A new extension from @angular/schematics called prompts has been introduced with the CLI that will now prompt developers when running common commands such as ng new my-app. Following is an example from the Angular Schematic for ng new:

"routing": {
  "type": "boolean",
  "description": "Generates a routing module.",
  "default": false,
  "x-prompt": "Would you like to add Angular routing?"
},

Angular 7 Environment Setup

Both the CLI and generated project have dependencies that require Node 10 and hence let us first install it on our local machine. You can download the latest version of Node js from here - Node Js Official. I have node 10.13.0 installed on my local which includes npm 6.4.1. To update NPM, you can run the following command in the terminal.

npm i npm@latest -g

If u have an older @angular/cli version, then you can run following command to install the latest versions:

npm uninstall -g @angular/cli
npm cache clean
npm install -g @angular/cli

You can run below commands to check the versions on your local machine.

 Other Interesting Posts
Angular PayuMoney Integration
Angular Server Side Rendering Example
Angular Material Sidenav Example
Spring Boot Angular Captcha
Deploy Angular App on Nginx
Angular 6 CRUD Example
Angular JWT Authentication
Angular Material App Example
Typescript Basic Tutorial
Spring Boot Angular Deployment
Spring Boot Angular Example

Generating Angular 7 Project from CLI

Traverse to your folder of choice and execute following commands to generate and run Angular 7 app.

ng new my-dream-app
cd my-dream-app
ng serve

Now, you can open your browser and hit localhost:4200 to see the welcome page. Below is the screenshot.

angular-7-welcome

Project Structure

Now, we can import the project in any IDE and check the the angular.json file to check the version of angular dependencies. Following are the different components that we will be creating. You can download the source from GitHub here.

ng g component login
ng g component add-user
ng g component edit-user
ng g component list-user

Here, is the final project structure that we will creating by the end of this tutorial.

angular7-project-strct

Angular 7 Login Component

For login component, we will have a reactive form that accepts username and password. On submit, /login API is called which validates user credentials from DB and returns user token. We will be caching this token in local storage. The same token will be used in the subsequent request. Post login, user list page will be rendered. We have all validations in place to check the required fields are not empty. In a similar fashion, you can add multiple other validations such as character min-length or email validations.

Following is the implementation.

login.component.html
<div class="row">

  <div class="col-md-6 login-container">
    <h2 style="margin: auto">Login </h2>
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" class="form-control" formControlName="username" id="username" autocomplete="off">
        <div class="error" *ngIf="loginForm.controls['username'].hasError('required') && loginForm.controls['username'].touched">Username is required</div>
      </div>
      <div class="form-group">
        <label for="pwd">Password:</label>
        <input type="password" class="form-control" formControlName="password" id="pwd" autocomplete="off">
        <div class="error" *ngIf="loginForm.controls['password'].hasError('required') && loginForm.controls['password'].touched">Password is required</div>
      </div>
      <button class="btn btn-success" [disabled]="loginForm.invalid">Login</button>
      <div *ngIf="invalidLogin" class="error">
        <div>Invalid credentials.</div>
      </div>
    </form>
  </div>
</div>
angular7-login

The ApiService has all the HTTP request implementation that makes HTTP request to our backend APIs.

login.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {Router} from "@angular/router";
import {ApiService} from "../core/api.service";

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  loginForm: FormGroup;
  invalidLogin: boolean = false;
  constructor(private formBuilder: FormBuilder, private router: Router, private apiService: ApiService) { }

  onSubmit() {
    if (this.loginForm.invalid) {
      return;
    }
    const loginPayload = {
      username: this.loginForm.controls.username.value,
      password: this.loginForm.controls.password.value
    }
    this.apiService.login(loginPayload).subscribe(data => {
      if(data.status === 200) {
        window.localStorage.setItem('token', data.result.token);
        this.router.navigate(['list-user']);
      }else {
        this.invalidLogin = true;
        alert(data.message);
      }
    });
  }

  ngOnInit() {
    window.localStorage.removeItem('token');
    this.loginForm = this.formBuilder.group({
      username: ['', Validators.compose([Validators.required])],
      password: ['', Validators.required]
    });
  }

}

Angular 7 Service Implementation

We have 6 API implementation for CRUD operations including login API for user authentication. For now, let us assume our user endpoint is localhost:8080/users. Below is the implementation.All the API response has uniform response data format as below.

{
    "status": 200,
    "message": "",
    "result": {}
	
	}
api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {User} from "../model/user.model";
import {Observable} from "rxjs/index";
import {ApiResponse} from "../model/api.response";

@Injectable()
export class ApiService {

  constructor(private http: HttpClient) { }
  baseUrl: string = 'http://localhost:8080/users/';

  login(loginPayload) : Observable<ApiResponse> {
    return this.http.post<ApiResponse>('http://localhost:8080/'+ 'token/generate-token', loginPayload).pipe(
catchError(val => of(val)));
};
  }

  getUsers() : Observable<ApiResponse> {
    return this.http.get<ApiResponse>(this.baseUrl);
  }

  getUserById(id: number): Observable<ApiResponse> {
    return this.http.get<ApiResponse>(this.baseUrl + id);
  }

  createUser(user: User): Observable<ApiResponse> {
    return this.http.post<ApiResponse>(this.baseUrl, user);
  }

  updateUser(user: User): Observable<ApiResponse> {
    return this.http.put<ApiResponse>(this.baseUrl + user.id, user);
  }

  deleteUser(id: number): Observable<ApiResponse> {
    return this.http.delete<ApiResponse>(this.baseUrl + id);
  }
}

Angular 7 User Components

List User

After a successfull login, list-user route will be loaded and getUsers() will be invoked that will load the list of users and the user list will be shown in a tabular form. Each row will have button to either update or delete a user entry. Add button will open a new form to add a new user. On click of edit button, edit component will be loaded with user details auto populated where we can edit the user details and save the changes and click on delete button will instantly delete the user from DB and update the table.Following is the implementation.

list-user.component.html
<div class="col-md-6 user-container">
  <h2 style="margin: auto"> User Details</h2>
  <button class="btn btn-danger" style="width:100px" (click)="addUser()"> Add User</button>
  <table class="table table-striped">
    <thead>
    <tr>
      <th class="hidden">Id</th>
      <th>FirstName</th>
      <th>LastName</th>
      <th>UserName</th>
      <th>Age</th>
      <th>Salary</th>
    </tr>
    </thead>
    <tbody>
    <tr *ngFor="let user of users">
      <td class="hidden">{{user.id}}</td>
      <td>{{user.firstName}}</td>
      <td>{{user.lastName}}</td>
      <td>{{user.username}}</td>
      <td>{{user.age}}</td>
      <td>{{user.salary}}</td>
      <td><button class="btn btn-success" (click)="deleteUser(user)"> Delete</button>
        <button class="btn btn-success" (click)="editUser(user)" style="margin-left: 20px;"> Edit</button></td>
    </tr>
    </tbody>
  </table>
</div>
list-user

Post login, all the APIs are secured and hence requires token to perform authentication amd authorization at the server. Instead of duplicating code to add the token in every request, we have implemented HTTPInterceptor that intercepts all the HTTP request and adds required token in the header. The implementation is in the subsequent heading.

list-user.component.ts
import { Component, OnInit , Inject} from '@angular/core';
import {Router} from "@angular/router";
import {User} from "../model/user.model";
import {ApiService} from "../core/api.service";

@Component({
  selector: 'app-list-user',
  templateUrl: './list-user.component.html',
  styleUrls: ['./list-user.component.css']
})
export class ListUserComponent implements OnInit {

  users: User[];

  constructor(private router: Router, private apiService: ApiService) { }

  ngOnInit() {
    if(!window.localStorage.getItem('token')) {
      this.router.navigate(['login']);
      return;
    }
    this.apiService.getUsers()
      .subscribe( data => {
          this.users = data.result;
      });
  }

  deleteUser(user: User): void {
    this.apiService.deleteUser(user.id)
      .subscribe( data => {
        this.users = this.users.filter(u => u !== user);
      })
  };

  editUser(user: User): void {
    window.localStorage.removeItem("editUserId");
    window.localStorage.setItem("editUserId", user.id.toString());
    this.router.navigate(['edit-user']);
  };

  addUser(): void {
    this.router.navigate(['add-user']);
  };
}

Edit User

Whenever, user clicks on the edit button, the selected id is stored in the cache first and when edit route is loaded, the cached id is used to fetch user details from the server and auto populate the edit user form.

edit-user.component.html
<div class="col-md-6 user-container">
  <h2 class="text-center">Edit User</h2>
  <form [formGroup]="editForm" (ngSubmit)="onSubmit()">
    <div class="hidden">
      <input type="text" formControlName="id" placeholder="id" name="id" class="form-control" id="id">
    </div>
    <div class="form-group">
      <label for="username">User Name:</label>
      <input type="text" formControlName="username" placeholder="username" name="username" class="form-control" id="username" readonly="true">
    </div>

    <div class="form-group">
      <label for="firstName">First Name:</label>
      <input formControlName="firstName" placeholder="First Name" name="firstName" class="form-control" id="firstName">
    </div>

    <div class="form-group">
      <label for="lastName">Last Name:</label>
      <input formControlName="lastName" placeholder="Last name" name="lastName" class="form-control" id="lastName">
    </div>

    <div class="form-group">
      <label for="age">Age:</label>
      <input type="number" formControlName="age" placeholder="age" name="age" class="form-control" id="age">
    </div>

    <div class="form-group">
      <label for="salary">Salary:</label>
      <input type="number" formControlName="salary" placeholder="salary" name="salary" class="form-control" id="salary">
    </div>

    <button class="btn btn-success">Update</button>
  </form>
</div>
edit-user edit-user.component.ts
import { Component, OnInit , Inject} from '@angular/core';
import {Router} from "@angular/router";
import {User} from "../model/user.model";
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {first} from "rxjs/operators";
import {ApiService} from "../core/api.service";

@Component({
  selector: 'app-edit-user',
  templateUrl: './edit-user.component.html',
  styleUrls: ['./edit-user.component.css']
})
export class EditUserComponent implements OnInit {

  user: User;
  editForm: FormGroup;
  constructor(private formBuilder: FormBuilder,private router: Router, private apiService: ApiService) { }

  ngOnInit() {
    let userId = window.localStorage.getItem("editUserId");
    if(!userId) {
      alert("Invalid action.")
      this.router.navigate(['list-user']);
      return;
    }
    this.editForm = this.formBuilder.group({
      id: [''],
      username: ['', Validators.required],
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      age: ['', Validators.required],
      salary: ['', Validators.required]
    });
    this.apiService.getUserById(+userId)
      .subscribe( data => {
        this.editForm.setValue(data.result);
      });
  }

  onSubmit() {
    this.apiService.updateUser(this.editForm.value)
      .pipe(first())
      .subscribe(
        data => {
          if(data.status === 200) {
            alert('User updated successfully.');
            this.router.navigate(['list-user']);
          }else {
            alert(data.message);
          }
        },
        error => {
          alert(error);
        });
  }

}

Add user

Similarly, we have add user implementation below.

add-user.component.html
<div class="col-md-6 user-container">
  <h2 class="text-center">Add User</h2>
  <form [formGroup]="addForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label for="username">User Name:</label>
      <input type="text" formControlName="username" placeholder="username" name="username" class="form-control" id="username">
    </div>

    <div class="form-group">
      <label for="password">Password:</label>
      <input type="password" formControlName="password" placeholder="password" name="password" class="form-control" id="password">
    </div>

    <div class="form-group">
      <label for="firstName">First Name:</label>
      <input formControlName="firstName" placeholder="First Name" name="firstName" class="form-control" id="firstName">
    </div>

    <div class="form-group">
      <label for="lastName">Last Name:</label>
      <input formControlName="lastName" placeholder="Last name" name="lastName" class="form-control" id="lastName">
    </div>

    <div class="form-group">
      <label for="age">Age:</label>
      <input type="number" formControlName="age" placeholder="age" name="age" class="form-control" id="age">
    </div>

    <div class="form-group">
      <label for="salary">Salary:</label>
      <input type="number" formControlName="salary" placeholder="salary" name="salary" class="form-control" id="salary">
    </div>

    <button class="btn btn-success">Update</button>
  </form>
</div>
add-user.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {Router} from "@angular/router";
import {ApiService} from "../core/api.service";

@Component({
  selector: 'app-add-user',
  templateUrl: './add-user.component.html',
  styleUrls: ['./add-user.component.css']
})
export class AddUserComponent implements OnInit {

  constructor(private formBuilder: FormBuilder,private router: Router, private apiService: ApiService) { }

  addForm: FormGroup;

  ngOnInit() {
    this.addForm = this.formBuilder.group({
      id: [],
      username: ['', Validators.required],
      password: ['', Validators.required],
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      age: ['', Validators.required],
      salary: ['', Validators.required]
    });

  }

  onSubmit() {
    this.apiService.createUser(this.addForm.value)
      .subscribe( data => {
        this.router.navigate(['list-user']);
      });
  }

}

Angular 7 HTTP Interceptor

The interceptor implements HttpInterceptor which intercepts all the HTTP request and token in the header for API authentication. Following is the implementation.

During login, there won't be any token present in the local cache hence, there is a condition check for presence of token.

interceptor.ts
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/internal/Observable";
import {Injectable} from "@angular/core";

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest, next: HttpHandler): Observable> {
    let token = window.localStorage.getItem('token');
    debugger
    if (token) {
      request = request.clone({
        setHeaders: {
          Authorization: 'Bearer ' + token
        }
      });
    }
    return next.handle(request);
  }
}

App Module Implementation

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { AddUserComponent } from './add-user/add-user.component';
import { EditUserComponent } from './edit-user/edit-user.component';
import { ListUserComponent } from './list-user/list-user.component';
import {ApiService} from "./core/api.service";
import {HTTP_INTERCEPTORS, HttpClientModule} from "@angular/common/http";
import {ReactiveFormsModule} from "@angular/forms";
import {routing} from "./app.routing";
import {TokenInterceptor} from "./core/interceptor";

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    AddUserComponent,
    EditUserComponent,
    ListUserComponent
  ],
  imports: [
    BrowserModule,
    routing,
    ReactiveFormsModule,
    HttpClientModule
  ],
  providers: [ApiService, {provide: HTTP_INTERCEPTORS,
    useClass: TokenInterceptor,
    multi : true}],
  bootstrap: [AppComponent]
})
export class AppModule { }


Spring Boot Server Implementation

We have created multiple applications in my previous articles on spring boot security. All the examples are available here - Spring Boot Security Apps. Here, again we will be creating another spring boot app and integrate JWT with it for authentication and authorization. You can actually follow Spring Boot Security JWT article for step by step explanation as we may skip common explanation and implementations here and you can download the source from here.

First, let us head over to start.spring.io to generate a sample spring boot project and then import it into your workspace and then make changes in it to integrate JWT. Following will be our spring boot project structure.

server-project-structure

JWT Token Validation

Following class extends OncePerRequestFilter that ensures a single execution per request dispatch. This class checks for the authorization header and authenticates the JWT token and sets the authentication in the context.Doing so will protect our APIs from those requests which do not have any authorization token.The configuration about which resource to be protected and which not can be configured in WebSecurityConfig.java

package com.devglan.config;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

import static com.devglan.model.Constants.HEADER_STRING;
import static com.devglan.model.Constants.TOKEN_PREFIX;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        String header = req.getHeader(HEADER_STRING);
        String username = null;
        String authToken = null;
        if (header != null && header.startsWith(TOKEN_PREFIX)) {
            authToken = header.replace(TOKEN_PREFIX,"");
            try {
                username = jwtTokenUtil.getUsernameFromToken(authToken);
            } catch (IllegalArgumentException e) {
                logger.error("an error occured during getting username from token", e);
            } catch (ExpiredJwtException e) {
                logger.warn("the token is expired and not valid anymore", e);
            } catch(SignatureException e){
                logger.error("Authentication Failed. Username or Password not valid.");
            }
        } else {
            logger.warn("couldn't find bearer string, will ignore the header");
        }
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
                logger.info("authenticated user " + username + ", setting security context");
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(req, res);
    }
}

Spring Boot Web Security Config

Below is the implementation of our security config that does not requires any authorization for endpoints token/* and /signup. Rest all the endpoints related to users are secured.

WebSecurityConfig.java
package com.devglan.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource(name = "userService")
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(encoder());
    }

    @Bean
    public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {
        return new JwtAuthenticationFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().
                authorizeRequests()
                .antMatchers("/token/*", "/signup").permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public BCryptPasswordEncoder encoder(){
        return new BCryptPasswordEncoder();
    }

}

Spring Boot Controller

We have 2 controller defined here. One for generating token i.e. sign up which is not secured and the other is usercontroller which requires authentication.

API Details

API Name  - Token Generation
URL - localhost:8080/token/generate-token
Method - POST
Header - Content-Type: application/json
Body -
{
	"username":"alex123",
	"password":"alex123"
}

Response :

{
    "status": 200,
    "message": "success",
    "result": {
        "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE",
        "username": "alex123"
    }
}

API Name  - List User
URL - http://localhost:8080/users
Method - Get
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE
Response -
{
    "status": 200,
    "message": "User list fetched successfully.",
    "result": [
        {
            "id": 1,
            "firstName": "Alex",
            "lastName": "Knr",
            "username": "alex123",
            "salary": 3456,
            "age": 33
        },
        {
            "id": 2,
            "firstName": "Tom",
            "lastName": "Asr",
            "username": "tom234",
            "salary": 7823,
            "age": 23
        },
        {
            "id": 3,
            "firstName": "Adam",
            "lastName": "Psr",
            "username": "adam",
            "salary": 4234,
            "age": 45
        }
    ]
}

API Name  - Create User
URL - http://localhost:8080/users
Method - POST
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE
Body -
{
	"username":"test",
	"password":"test",
	"firstName":"test",
	"lastName":"test",
	"age":23,
	"salary":12345
}

Response -

{
    "status": 200,
    "message": "User saved successfully.",
    "result": {
        "id": 4,
        "firstName": "test",
        "lastName": "test"",
        "username": "test",
        "salary": 12345,
        "age": 23
    }
}


API Name  - Update User
URL - http://localhost:8080/users/4
Method - PUT
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE
Body -
{
	"username":"test1",
	"password":"test1",
	"firstName":"test1",
	"lastName":"test1",
	"age":24,
	"salary":12345
}

Response -

{
    "status": 200,
    "message": "User updated successfully.",
    "result": {
        "id": 0,
        "firstName": "test1",
        "lastName": "test1",
        "username": "test1",
        "password": "test1",
        "age": 24,
        "salary": 12345
    }
}

API Name  - Delete User
URL - http://localhost:8080/users/4
Method - DELETE
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE

Response -
{
    "status": 200,
    "message": "User deleted successfully.",
    "result": null
}
AuthenticationController.java
package com.devglan.controller;

import com.devglan.config.JwtTokenUtil;
import com.devglan.model.*;
import com.devglan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/token")
public class AuthenticationController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/generate-token", method = RequestMethod.POST)
    public ApiResponse register(@RequestBody LoginUser loginUser) throws AuthenticationException {

        authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword()));
        final User user = userService.findOne(loginUser.getUsername());
        final String token = jwtTokenUtil.generateToken(user);
        return new ApiResponse<>(200, "success",new AuthToken(token, user.getUsername()));
    }

}

UserController.java
package com.devglan.controller;

import com.devglan.model.ApiResponse;
import com.devglan.model.User;
import com.devglan.model.UserDto;
import com.devglan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ApiResponse saveUser(@RequestBody UserDto user){
        return new ApiResponse<>(HttpStatus.OK.value(), "User saved successfully.",userService.save(user));
    }

    @GetMapping
    public ApiResponse> listUser(){
        return new ApiResponse<>(HttpStatus.OK.value(), "User list fetched successfully.",userService.findAll());
    }

    @GetMapping("/{id}")
    public ApiResponse getOne(@PathVariable int id){
        return new ApiResponse<>(HttpStatus.OK.value(), "User fetched successfully.",userService.findById(id));
    }

    @PutMapping("/{id}")
    public ApiResponse update(@RequestBody UserDto userDto) {
        return new ApiResponse<>(HttpStatus.OK.value(), "User updated successfully.",userService.update(userDto));
    }

    @DeleteMapping("/{id}")
    public ApiResponse delete(@PathVariable int id) {
        userService.delete(id);
        return new ApiResponse<>(HttpStatus.OK.value(), "User fetched successfully.", null);
    }



}

Spring Boot Service implementation

The service implementation is very simple which communicates with spring data JPA to fetch rows from DB. One thing to remeber here is that we are saving Bcrypt encoded password in the DB.

UserService.java
package com.devglan.service.impl;

import com.devglan.dao.UserDao;
import com.devglan.model.User;
import com.devglan.model.UserDto;
import com.devglan.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;


@Service(value = "userService")
public class UserServiceImpl implements UserDetailsService, UserService {
	
	@Autowired
	private UserDao userDao;

	@Autowired
	private BCryptPasswordEncoder bcryptEncoder;

	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userDao.findByUsername(username);
		if(user == null){
			throw new UsernameNotFoundException("Invalid username or password.");
		}
		return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority());
	}

	private List getAuthority() {
		return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
	}

	public List findAll() {
		List list = new ArrayList<>();
		userDao.findAll().iterator().forEachRemaining(list::add);
		return list;
	}

	@Override
	public void delete(int id) {
		userDao.deleteById(id);
	}

	@Override
	public User findOne(String username) {
		return userDao.findByUsername(username);
	}

	@Override
	public User findById(int id) {
		Optional optionalUser = userDao.findById(id);
		return optionalUser.isPresent() ? optionalUser.get() : null;
	}

    @Override
    public UserDto update(UserDto userDto) {
        User user = findById(userDto.getId());
        if(user != null) {
            BeanUtils.copyProperties(userDto, user, "password");
            userDao.save(user);
        }
        return userDto;
    }

    @Override
    public User save(UserDto user) {
	    User newUser = new User();
	    newUser.setUsername(user.getUsername());
	    newUser.setFirstName(user.getFirstName());
	    newUser.setLastName(user.getLastName());
	    newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
		newUser.setAge(user.getAge());
		newUser.setSalary(user.getSalary());
        return userDao.save(newUser);
    }
}

Model Class Implementation

User.java
package com.devglan.model;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private int id;
    @Column
    private String firstName;
    @Column
    private String lastName;
    @Column
    private String username;
    @Column
    @JsonIgnore
    private String password;
    @Column
    private long salary;
    @Column
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public long getSalary() {
        return salary;
    }

    public void setSalary(long salary) {
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

UserDto.java
package com.devglan.model;

public class UserDto {

    private int id;
    private String firstName;
    private String lastName;
    private String username;
    private String password;
    private int age;
    private int salary;

}

Following is the DB config we are using.

application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.user.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

Below is the DB scripts.

CREATE TABLE user(id INT NOT NULL AUTO_INCREMENT,age INT,firstName VARCHAR(255),lastName VARCHAR(255),password VARCHAR(255),salary BIGINT,username VARCHAR(255),PRIMARY KEY (id))ENGINE=MyISAM;

INSERT INTO User (id, firstname, lastname, username, password, salary, age) VALUES (1, 'Alex','Knr', 'alex123','$2a$04$4vwa/ugGbBVDvbWaKUVZBuJbjyQyj6tqntjSmG8q.hi97.xSdhj/2', 3456, 33);
INSERT INTO User (id, firstname, lastname, username, password, salary, age)  VALUES (2, 'Tom', 'Asr', 'tom234', '$2a$04$QED4arFwM1AtQWkR3JkQx.hXxeAk/G45NiRd3Q4ElgZdzGYCYKZOW', 7823, 23);
INSERT INTO User (id, firstname, lastname, username, password, salary, age)  VALUES (3, 'Adam', 'Psr', 'adam', '$2a$04$WeT1SvJaGjmvQj34QG8VgO9qdXecKOYKEDZtCPeeIBSTxxEhazNla', 4234, 45);

Adding CORS with Spring Boot Angular

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. Since, we have server running at localhost:8080 and angular running at localhost:4200, we see error as No 'Access-Control-Allow-Origin'. To avoid this error, we can add below filter in the spring project.

package com.devglan.config;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;


public class CORSFilter implements Filter {

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
		System.out.println("Filtering on...........................................................");
		HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
		response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers");

		chain.doFilter(req, res);
	}

	public void init(FilterConfig filterConfig) {}

	public void destroy() {}

}

Conclusion

This is pretty much implementation of a full stack app using Angular 7 and Spring Boot with JWT authentication. I know you need the source code now. You can download it from this link for the same. It has both the server and client project.If you want to add anything else then please let me know in the comments below.

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Angular JS