How to access deeply nested array with MongoDB (ASP.NET Core 2.2)

I'm designing an inventory management system with MongoDB. I have the following database structure:

I'm designing an inventory management system with MongoDB. I have the following database structure:

inventory
└─storage_slots
└─storage_locations
...etc...

Every time a new Slot is added, a tree representing the slot's location in the hierarchy is added to the storage_locations collection to represent its location (according to location, room, section, shelf). So far I have managed to successfully add a new item where none of the location fields are already used: (The slot is also added to the storage_slots collection)

{
"_id" : ObjectId("5c57169f0863d665c7f13d27"),
"CreatedUtc" : {
    "$date" : 1549211298017
},
"UpdatedUtc" : {
    "$date" : 1549211298017
},
"Description" : null,
"Address" : null,
"StorageRooms" : [
    {
        "_id" : ObjectId("5c57169f0863d665c7f13d28"),
        "CreatedUtc" : {
            "$date" : 1549211297719
        },
        "UpdatedUtc" : {
            "$date" : 1549211297719
        },
        "Description" : null,
        "StorageSections" : [
            {
                "_id" : ObjectId("5c57169f0863d665c7f13d29"),
                "CreatedUtc" : {
                    "$date" : 1549211297719
                },
                "UpdatedUtc" : {
                    "$date" : 1549211297719
                },
                "Description" : null,
                "StorageShelves" : [
                    {
                        "_id" : ObjectId("5c57169f0863d665c7f13d2a"),
                        "CreatedUtc" : {
                            "$date" : 1549211297719
                        },
                        "UpdatedUtc" : {
                            "$date" : 1549211297719
                        },
                        "Description" : null,
                        "StorageSlotIds" : [
                            ObjectId("5c57169f0863d665c7f13d26")
                        ]
                    }
                ]
            }
        ]
    }
]
}

To be clear, storage_locations is the above hierarchy while storage_slots is just a collection of slots.

However, if the fields are already present in the hierarchy, the following code is run: (I took insipration from this post)

var filter = Builders<StorageLocation>.Filter.And(
            Builders<StorageLocation>.Filter.Where(location => location.Id == id),
            Builders<StorageLocation>.Filter.Eq("StorageRooms.Id", roomId),
            Builders<StorageLocation>.Filter.Eq("StorageRooms.$.StorageSections.Id", sectionId),
            Builders<StorageLocation>.Filter.Eq("StorageRooms.$.StorageSections.$.StorageShelves.Id", shelfId));
        var update =
            Builders<StorageLocation>.Update.Push("StorageRooms.$.StorageSections.$.StorageShelves.$.StorageSlotIds",
                storageSlotIds);
        return await UpdateAsync(filter, update, cancellationToken);

Also, if only some of them are defined then I do a mix of both that I decided not to show here because they are built on the same principles and would not contribute to the question.

The problem

Whenever the code directly above is run. I get the following error:

InvalidCastException: Unable to cast object of type 'MongoDB.Bson.ObjectId[]' to type 'MongoDB.Bson.ObjectId'.

MongoDB.Bson.Serialization.Serializers.SerializerBase<TValue>.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)

//annoying scrollbar

The error occurs on this line:

return await UpdateAsync(filter, update, cancellationToken);

The method is:

public Task<UpdateResult> UpdateAsync(FilterDefinition<T> filter, UpdateDefinition<T> updateDefinition,
string database, string collection, CancellationToken cancellationToken)
{
return _mongoContext.MongoClient.GetDatabase(database).GetCollection<T>(collection)
.UpdateOneAsync(filter, updateDefinition.Set(o => o.UpdatedUtc, DateTime.UtcNow),
cancellationToken: cancellationToken);
}
Extra stuff

Here are some more relevant classes for the question:

public class StorageLocation : Dbo
{
public string Description { get; set; }
public Address Address { get; set; }
public IEnumerable<StorageRoom> StorageRooms { get; set; }
}
public class StorageRoom : Dbo
{
public string Description { get; set; }
public IEnumerable<StorageSection> StorageSections { get; set; }
}
public class StorageSection : Dbo
{
public string Description { get; set; }
public IEnumerable<StorageShelf> StorageShelves { get; set; }
}
public class StorageShelf : Dbo
{
public string Description { get; set; }
public IEnumerable<ObjectId> StorageSlotIds { get; set; }
}
public class StorageSlot : Dbo
{
public string Description { get; set; }

public ObjectId LocationId { get; set; }
public ObjectId RoomId { get; set; }
public ObjectId SectionId { get; set; }
public ObjectId ShelfId { get; set; }

...etc...

}


Securing RESTful API with Spring Boot, Security, and Data MongoDB

Securing RESTful API with Spring Boot, Security, and Data MongoDB

A comprehensive step by step tutorial on securing or authentication RESTful API with Spring Boot, Security, and Data MongoDB

A comprehensive step by step tutorial on securing or authentication RESTful API with Spring Boot, Security, and Data MongoDB. Previously, we have shown you how to securing Spring Boot, MVC and MongoDB web application. In this tutorial, the secure endpoint will restrict the access from an unauthorized request. Every request to secure endpoint should bring authorization token with it. Of course, there will be an endpoint for login which will get authorization token after successful login.

Table of Contents:

The following software, tools, and frameworks are required for this tutorial:

We assume that you already installed all required software, tools, and frameworks. So, we will not cover how to install that software, tools, and frameworks.

1. Generate a New Spring Boot Gradle Project

To create or generate a new Spring Boot Application or Project, simply go to Spring Initializer. Fill all required fields as below then click on Generate Project button.

The project will automatically be downloaded as a Zip file. Next, extract the zipped project to your java projects folder. On the project folder root, you will find build.gradle file for register dependencies, initially it looks like this.

buildscript {
&nbsp;&nbsp; &nbsp;ext {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;springBootVersion = '2.1.2.RELEASE'
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;repositories {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;mavenCentral()
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;dependencies {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
&nbsp;&nbsp; &nbsp;}
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.djamware'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
&nbsp;&nbsp; &nbsp;mavenCentral()
}

dependencies {
&nbsp;&nbsp; &nbsp;implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
&nbsp;&nbsp; &nbsp;implementation 'org.springframework.boot:spring-boot-starter-security'
&nbsp;&nbsp; &nbsp;implementation 'org.springframework.boot:spring-boot-starter-web'
&nbsp;&nbsp; &nbsp;testImplementation 'org.springframework.boot:spring-boot-starter-test'
&nbsp;&nbsp; &nbsp;testImplementation 'org.springframework.security:spring-security-test'
}

Now, you can work with the source code of this Spring Boot Project using your own IDE or Text Editor. We are using Spring Tool Suite (STS). In STS, import the extracted zipped file as Existing Gradle Project.

Next, we have to add the JWT library to the build.gradle as the dependency. Open and edit build.gradle then add this line to dependencies after other implementation.

implementation 'io.jsonwebtoken:jjwt:0.9.1'

Next, compile the Gradle Project by type this command from Terminal or CMD.

./gradlew compile

Or you can compile directly from STS by right-clicking the project name then choose Gradle -> Refresh Gradle Project. Next, open and edit src/main/resources/application.properties then add these lines.

spring.data.mongodb.database=springmongodb
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017

2. Create Product, User and Role Model or Entity Classes

We will be creating all required models or entities for products, user and role. In STS, right-click the project name -> New -> Class. Fill the package with com.djamware.SecurityRest.models, the name with Product, and leave other fields and checkbox as default then click Finish Button.

Next, open and edit src/main/java/com/djamware/SecurityRest/models/Product.java then add this annotation above the class name that will point to MongoDB collection.

@Document(collection = "products")

Inside Product class, add these variables.

@Id
String id;
String prodName;
String prodDesc;
Double prodPrice;
String prodImage;

Add constructors after the variable or fields.

public Product() {
}

public Product(String prodName, String prodDesc, Double prodPrice, String prodImage) {
&nbsp;&nbsp; &nbsp;super();
&nbsp;&nbsp; &nbsp;this.prodName = prodName;
&nbsp;&nbsp; &nbsp;this.prodDesc = prodDesc;
&nbsp;&nbsp; &nbsp;this.prodPrice = prodPrice;
&nbsp;&nbsp; &nbsp;this.prodImage = prodImage;
}

Generate or create Getter and Setter for each field.

public String getId() {
&nbsp;&nbsp; &nbsp;return id;
}

public void setId(String id) {
&nbsp;&nbsp; &nbsp;this.id = id;
}

public String getProdName() {
&nbsp;&nbsp; &nbsp;return prodName;
}

public void setProdName(String prodName) {
&nbsp;&nbsp; &nbsp;this.prodName = prodName;
}

public String getProdDesc() {
&nbsp;&nbsp; &nbsp;return prodDesc;
}

public void setProdDesc(String prodDesc) {
&nbsp;&nbsp; &nbsp;this.prodDesc = prodDesc;
}

public Double getProdPrice() {
&nbsp;&nbsp; &nbsp;return prodPrice;
}

public void setProdPrice(Double prodPrice) {
&nbsp;&nbsp; &nbsp;this.prodPrice = prodPrice;
}

public String getProdImage() {
&nbsp;&nbsp; &nbsp;return prodImage;
}

public void setProdImage(String prodImage) {
&nbsp;&nbsp; &nbsp;this.prodImage = prodImage;
}

Using STS you can organize imports automatically from the menu Source -> Organize Imports then you can see the imports after the package name.

package com.djamware.SecurityRest.models;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

You can do the same way as the above step for User and Role class. Here’s the User class looks like.

package com.djamware.SecurityRest.models;

import java.util.Set;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "users")
public class User {

&nbsp;&nbsp; &nbsp;@Id
&nbsp;&nbsp; &nbsp;private String id;
&nbsp;&nbsp; &nbsp;@Indexed(unique = true, direction = IndexDirection.DESCENDING, dropDups = true)
&nbsp;&nbsp; &nbsp;private String email;
&nbsp;&nbsp; &nbsp;private String password;
&nbsp;&nbsp; &nbsp;private String fullname;
&nbsp;&nbsp; &nbsp;private boolean enabled;
&nbsp;&nbsp; &nbsp;@DBRef
&nbsp;&nbsp; &nbsp;private Set<Role> roles;
&nbsp;&nbsp; &nbsp;public String getId() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return id;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setId(String id) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.id = id;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public String getEmail() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return email;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setEmail(String email) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.email = email;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public String getPassword() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return password;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setPassword(String password) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.password = password;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public String getFullname() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return fullname;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setFullname(String fullname) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.fullname = fullname;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public boolean isEnabled() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return enabled;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setEnabled(boolean enabled) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.enabled = enabled;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public Set<Role> getRoles() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return roles;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setRoles(Set<Role> roles) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.roles = roles;
&nbsp;&nbsp; &nbsp;}

}

And the Role class will be like this.

package com.djamware.SecurityRest.models;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "roles")
public class Role {

&nbsp;&nbsp; &nbsp;@Id
&nbsp; &nbsp; private String id;
&nbsp; &nbsp; @Indexed(unique = true, direction = IndexDirection.DESCENDING, dropDups = true)

&nbsp; &nbsp; private String role;
&nbsp;&nbsp; &nbsp;public String getId() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return id;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setId(String id) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.id = id;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public String getRole() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return role;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setRole(String role) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.role = role;
&nbsp;&nbsp; &nbsp;}

}

3. Create Product, User and Role Repository Interfaces

Next steps to create Product, User, and Role Repository Interfaces. From the STS, right-click the project name -> New -> Interface then fill all required fields and checkboxes as below before click Finish button.

Next, open and edit src/main/java/com/djamware/SecurityRest/repositories/ProductRepository.java then add extends to MongoDB CRUD Repository.

public interface ProductRepository extends CrudRepository<Product, String> {

}

Inside the class name add this method.

@Override
void delete(Product deleted);

Organize all required imports.

import org.springframework.data.repository.CrudRepository;
import com.djamware.SecurityRest.models.Product;

The same way can be applied to User and Role repositories. So, the User Repository Interface will look like this.

package com.djamware.SecurityRest.repositories;

import org.springframework.data.mongodb.repository.MongoRepository;
import com.djamware.SecurityRest.models.User;

public interface UserRepository extends MongoRepository<User, String> {

&nbsp;&nbsp; &nbsp;User findByEmail(String email);
}

And the Role Repository Interface will look like this.

package com.djamware.SecurityRest.repositories;

import org.springframework.data.mongodb.repository.MongoRepository;
import com.djamware.SecurityRest.models.Role;

public interface RoleRepository extends MongoRepository<Role, String> {

&nbsp;&nbsp; &nbsp;Role findByRole(String role);
}

4. Create a Custom User Details Service

To implements authentication using existing User and Role from MongoDB, we have to create a custom user details service. From the STS, right-click the project name -> New -> Class File then fill all required fields and checkboxes as below before clicking the finish button.

Next, open and edit src/main/java/com/djamware/SecurityRest/services/CustomUserDetailsService.java then give an annotation above the class name and implement the Spring Security User Details Service.

@Service
public class CustomUserDetailsService implements UserDetailsService {
}

Next, inject all required beans at the first line of the class bracket.

@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;

@Autowired
private PasswordEncoder bCryptPasswordEncoder;

Add a method to find a user by email field.

public User findUserByEmail(String email) {
&nbsp; &nbsp; return userRepository.findByEmail(email);
}

Add a method to save a new user.

public void saveUser(User user) {
&nbsp; &nbsp; user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
&nbsp; &nbsp; user.setEnabled(true);
&nbsp; &nbsp; Role userRole = roleRepository.findByRole("ADMIN");
&nbsp; &nbsp; user.setRoles(new HashSet<>(Arrays.asList(userRole)));
&nbsp; &nbsp; userRepository.save(user);
}

Override the Spring Security User Details to load User by email.

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

&nbsp; &nbsp; User user = userRepository.findByEmail(email);
&nbsp; &nbsp; if(user != null) {
&nbsp; &nbsp; &nbsp; &nbsp; List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
&nbsp; &nbsp; &nbsp; &nbsp; return buildUserForAuthentication(user, authorities);
&nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; throw new UsernameNotFoundException("username not found");
&nbsp; &nbsp; }
}

Add a method to get a set of Roles that related to a user.

private List<GrantedAuthority> getUserAuthority(Set<Role> userRoles) {
&nbsp; &nbsp; Set<GrantedAuthority> roles = new HashSet<>();
&nbsp; &nbsp; userRoles.forEach((role) -> {
&nbsp; &nbsp; &nbsp; &nbsp; roles.add(new SimpleGrantedAuthority(role.getRole()));
&nbsp; &nbsp; });

&nbsp; &nbsp; List<GrantedAuthority> grantedAuthorities = new ArrayList<>(roles);
&nbsp; &nbsp; return grantedAuthorities;
}

Add a method for authentication purpose.

private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
&nbsp; &nbsp; return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}

5. Configure Spring Boot Security Rest

Now, the main purpose of this tutorial is configuring Spring Security Rest. First, we have to create a bean for JWT token generation and validation. Right-click the project name -> New -> Class File. Fill the package name as com.djamware.SecurityRest.configs and the Class name as JwtTokenProvider then click the Finish button. Next, open and edit that newly created class file then give it an annotation above the class name.

@Component
public class JwtTokenProvider {
}

Add variables and injected bean inside the class bracket at the top lines.

@Value("${security.jwt.token.secret-key:secret}")
private String secretKey = "secret";

@Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds = 3600000; // 1h

@Autowired
private CustomUserDetailsService userDetailsService;

Add a method for initialization.

@PostConstruct
protected void init() {
&nbsp; &nbsp; secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}

Add a method to create a JWT token.

public String createToken(String username, Set<Role> set) {
&nbsp; &nbsp; Claims claims = Jwts.claims().setSubject(username);
&nbsp; &nbsp; claims.put("roles", set);
&nbsp; &nbsp; Date now = new Date();
&nbsp; &nbsp; Date validity = new Date(now.getTime() + validityInMilliseconds);
&nbsp; &nbsp; return Jwts.builder()//
&nbsp; &nbsp; &nbsp; &nbsp; .setClaims(claims)//
&nbsp; &nbsp; &nbsp; &nbsp; .setIssuedAt(now)//
&nbsp; &nbsp; &nbsp; &nbsp; .setExpiration(validity)//
&nbsp; &nbsp; &nbsp; &nbsp; .signWith(SignatureAlgorithm.HS256, secretKey)//
&nbsp; &nbsp; &nbsp; &nbsp; .compact();
}

Add a method to load User by username.

public Authentication getAuthentication(String token) {
&nbsp; &nbsp; UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
&nbsp; &nbsp; return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

Add a method to get the username by JWT token.

public String getUsername(String token) {
&nbsp; &nbsp; return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}

Add a method to resolve JWT token from request headers of Authorization that has a Bearer prefix.

public String resolveToken(HttpServletRequest req) {
&nbsp; &nbsp; String bearerToken = req.getHeader("Authorization");
&nbsp; &nbsp; if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
&nbsp; &nbsp; &nbsp; &nbsp; return bearerToken.substring(7, bearerToken.length());
&nbsp; &nbsp; }
&nbsp; &nbsp; return null;
}

Add a method to validate a JWT token.

public boolean validateToken(String token) {
&nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
&nbsp; &nbsp; &nbsp; &nbsp; if (claims.getBody().getExpiration().before(new Date())) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; return true;
&nbsp; &nbsp; } catch (JwtException | IllegalArgumentException e) {
&nbsp; &nbsp; &nbsp; &nbsp; throw new JwtException("Expired or invalid JWT token");
&nbsp; &nbsp; }
}

Finally, organize imports like below.

package com.djamware.SecurityRest.configs;

import java.util.Base64;
import java.util.Date;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import com.djamware.SecurityRest.models.Role;
import com.djamware.SecurityRest.services.CustomUserDetailsService;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

Next, create a JWT filter class with the name JwtTokenFilter in configs package that extends Spring GenericFilterBean. Replace all Java codes with these lines of codes.

package com.djamware.SecurityRest.configs;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

public class JwtTokenFilter extends GenericFilterBean {

&nbsp;&nbsp; &nbsp;private JwtTokenProvider jwtTokenProvider;

&nbsp; &nbsp; public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
&nbsp; &nbsp; &nbsp; &nbsp; this.jwtTokenProvider = jwtTokenProvider;
&nbsp; &nbsp; }

&nbsp; &nbsp; @Override
&nbsp; &nbsp; public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
&nbsp; &nbsp; &nbsp; &nbsp; throws IOException, ServletException {
&nbsp; &nbsp; &nbsp; &nbsp; String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
&nbsp; &nbsp; &nbsp; &nbsp; if (token != null && jwtTokenProvider.validateToken(token)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SecurityContextHolder.getContext().setAuthentication(auth);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; filterChain.doFilter(req, res);
&nbsp; &nbsp; }
}

Next, create a class with the name JwtConfigurer for JWT configuration in configs package then replace all codes with these lines of codes.

package com.djamware.SecurityRest.configs;

import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

&nbsp;&nbsp; &nbsp;private JwtTokenProvider jwtTokenProvider;

&nbsp; &nbsp; public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
&nbsp; &nbsp; &nbsp; &nbsp; this.jwtTokenProvider = jwtTokenProvider;
&nbsp; &nbsp; }

&nbsp; &nbsp; @Override
&nbsp; &nbsp; public void configure(HttpSecurity http) throws Exception {
&nbsp; &nbsp; &nbsp; &nbsp; JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
&nbsp; &nbsp; &nbsp; &nbsp; http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
&nbsp; &nbsp; }
}

Finally, we have to configure the Spring Security by creating a Java class file inside configs package with the name WebSecurityConfig. Give annotations to this class and extends Spring WebSecurityConfigurerAdapter.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}

Inject JWT token provider inside this class.

@Autowired
JwtTokenProvider jwtTokenProvider;

Add an override method to configure Authentication Manager Builder.

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
&nbsp;&nbsp; &nbsp;UserDetailsService userDetailsService = mongoUserDetails();
&nbsp;&nbsp; &nbsp;auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());

}

Next, add an override method to configure Spring Security Http Security.

@Override
protected void configure(HttpSecurity http) throws Exception {
&nbsp;&nbsp; &nbsp;http.httpBasic().disable().csrf().disable().sessionManagement()
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;.antMatchers("/api/auth/login").permitAll().antMatchers("/api/auth/register").permitAll()
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;.antMatchers("/api/products/**").hasAuthority("ADMIN").anyRequest().authenticated().and().csrf()
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;.disable().exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint()).and()
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;.apply(new JwtConfigurer(jwtTokenProvider));
}

Next, declare all required beans for this configuration.

@Bean
public PasswordEncoder bCryptPasswordEncoder() {
&nbsp;&nbsp; &nbsp;return new BCryptPasswordEncoder();
}

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

@Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
&nbsp;&nbsp; &nbsp;return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;"Unauthorized");
}

@Bean
public UserDetailsService mongoUserDetails() {
&nbsp;&nbsp; &nbsp;return new CustomUserDetailsService();
}

6. Create Product and Authentication Controllers

Now it’s time for REST API endpoint. All RESTful API will be created from each controller. Product controller will handle CRUD endpoint of product and Authentication controller will handle login and register endpoint. Right-click project name -> New -> Class then fill the package with com.djamware.SecurityRest.controllers and the class name as ProductController. Open and edit the newly created class file then replace all codes with these lines of codes.

package com.djamware.SecurityRest.controllers;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
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;

import com.djamware.SecurityRest.models.Product;
import com.djamware.SecurityRest.repositories.ProductRepository;

@RestController
public class ProductController {

&nbsp;&nbsp; &nbsp;@Autowired
&nbsp; &nbsp; ProductRepository productRepository;

&nbsp; &nbsp; @RequestMapping(method=RequestMethod.GET, value="/api/products")
&nbsp; &nbsp; public Iterable<Product> product() {
&nbsp; &nbsp; &nbsp; &nbsp; return productRepository.findAll();
&nbsp; &nbsp; }

&nbsp; &nbsp; @RequestMapping(method=RequestMethod.POST, value="/api/products")
&nbsp; &nbsp; public String save(@RequestBody Product product) {
&nbsp; &nbsp; &nbsp; &nbsp; productRepository.save(product);

&nbsp; &nbsp; &nbsp; &nbsp; return product.getId();
&nbsp; &nbsp; }

&nbsp; &nbsp; @RequestMapping(method=RequestMethod.GET, value="/api/products/{id}")
&nbsp; &nbsp; public Optional<Product> show(@PathVariable String id) {
&nbsp; &nbsp; &nbsp; &nbsp; return productRepository.findById(id);
&nbsp; &nbsp; }

&nbsp; &nbsp; @RequestMapping(method=RequestMethod.PUT, value="/api/products/{id}")
&nbsp; &nbsp; public Product update(@PathVariable String id, @RequestBody Product product) {
&nbsp; &nbsp; &nbsp;&nbsp; &nbsp;Optional<Product> prod = productRepository.findById(id);
&nbsp; &nbsp; &nbsp; &nbsp; if(product.getProdName() != null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prod.get().setProdName(product.getProdName());
&nbsp; &nbsp; &nbsp; &nbsp; if(product.getProdDesc() != null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prod.get().setProdDesc(product.getProdDesc());
&nbsp; &nbsp; &nbsp; &nbsp; if(product.getProdPrice() != null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prod.get().setProdPrice(product.getProdPrice());
&nbsp; &nbsp; &nbsp; &nbsp; if(product.getProdImage() != null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prod.get().setProdImage(product.getProdImage());
&nbsp; &nbsp; &nbsp; &nbsp; productRepository.save(prod.get());
&nbsp; &nbsp; &nbsp; &nbsp; return prod.get();
&nbsp; &nbsp; }

&nbsp; &nbsp; @RequestMapping(method=RequestMethod.DELETE, value="/api/products/{id}")
&nbsp; &nbsp; public String delete(@PathVariable String id) {
&nbsp; &nbsp; &nbsp; &nbsp; Optional<Product> product = productRepository.findById(id);
&nbsp; &nbsp; &nbsp; &nbsp; productRepository.delete(product.get());

&nbsp; &nbsp; &nbsp; &nbsp; return "product deleted";
&nbsp; &nbsp; }
}

For login, we need to create a POJO to mapping required fields of User. Create a new class file with the name AuthBody inside controllers package then replace all Java codes with these lines of codes.

package com.djamware.SecurityRest.controllers;

public class AuthBody {

&nbsp;&nbsp; &nbsp;private String email;
&nbsp; &nbsp; private String password;

&nbsp;&nbsp; &nbsp;public String getEmail() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return email;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setEmail(String email) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.email = email;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public String getPassword() {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return password;
&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;public void setPassword(String password) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;this.password = password;
&nbsp;&nbsp; &nbsp;}

}

Finally, create a controller for authentication with the name AuthController inside the controllers’ package. Open and edit that newly created file then replace all Java codes with these lines of codes.

package com.djamware.SecurityRest.controllers;

import static org.springframework.http.ResponseEntity.ok;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
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 com.djamware.SecurityRest.configs.JwtTokenProvider;
import com.djamware.SecurityRest.models.User;
import com.djamware.SecurityRest.repositories.UserRepository;
import com.djamware.SecurityRest.services.CustomUserDetailsService;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

&nbsp;&nbsp; &nbsp;@Autowired
&nbsp;&nbsp; &nbsp;AuthenticationManager authenticationManager;

&nbsp;&nbsp; &nbsp;@Autowired
&nbsp;&nbsp; &nbsp;JwtTokenProvider jwtTokenProvider;

&nbsp;&nbsp; &nbsp;@Autowired
&nbsp;&nbsp; &nbsp;UserRepository users;

&nbsp;&nbsp; &nbsp;@Autowired
&nbsp;&nbsp; &nbsp;private CustomUserDetailsService userService;

&nbsp;&nbsp; &nbsp;@SuppressWarnings("rawtypes")
&nbsp;&nbsp; &nbsp;@PostMapping("/login")
&nbsp;&nbsp; &nbsp;public ResponseEntity login(@RequestBody AuthBody data) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;String username = data.getEmail();
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword()));
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;String token = jwtTokenProvider.createToken(username, this.users.findByEmail(username).getRoles());
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Map<Object, Object> model = new HashMap<>();
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;model.put("username", username);
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;model.put("token", token);
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return ok(model);
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;} catch (AuthenticationException e) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;throw new BadCredentialsException("Invalid email/password supplied");
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;}

&nbsp;&nbsp; &nbsp;@SuppressWarnings("rawtypes")
&nbsp;&nbsp; &nbsp;@PostMapping("/register")
&nbsp;&nbsp; &nbsp;public ResponseEntity register(@RequestBody User user) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;User userExists = userService.findUserByEmail(user.getEmail());
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (userExists != null) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;throw new BadCredentialsException("User with username: " + user.getEmail() + " already exists");
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;userService.saveUser(user);
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Map<Object, Object> model = new HashMap<>();
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;model.put("message", "User registered successfully");
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return ok(model);
&nbsp;&nbsp; &nbsp;}
}

7. Run and Test Spring Boot Security Rest using Postman

Before run and test the application, we have to populate a Role data first. Open and edit src/main/java/com/djamware/SecurityRest/SecurityRestApplication.java then add these lines of codes inside the initialization method.

@Bean
CommandLineRunner init(RoleRepository roleRepository) {

&nbsp; &nbsp; return args -> {

&nbsp; &nbsp; &nbsp; &nbsp; Role adminRole = roleRepository.findByRole("ADMIN");
&nbsp; &nbsp; &nbsp; &nbsp; if (adminRole == null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Role newAdminRole = new Role();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; newAdminRole.setRole("ADMIN");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; roleRepository.save(newAdminRole);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; };

}

Next, make sure you have run the MongoDB server on your local machine then run the Gradle application using this command.

./gradlew bootRun

Or in STS just right-click the project name -> Run As -> Spring Boot App. Next, open the Postman application then change the method to GET and address to localhost:8080/api/products then click Send button.

You will see this response in the bottom panel of Postman.

{
&nbsp; &nbsp; "timestamp": "2019-03-07T13:16:34.935+0000",
&nbsp; &nbsp; "status": 401,
&nbsp; &nbsp; "error": "Unauthorized",
&nbsp; &nbsp; "message": "Unauthorized",
&nbsp; &nbsp; "path": "/api/products"
}

Next, change the method to POST then address to localhost:8080/api/auth/register then fill the body with raw data as below image then click Send button.

You will get the response in the bottom panel of Postman.

{
&nbsp; &nbsp; "message": "User registered successfully"
}

Next, change the address to localhost:8080/api/auth/login and change the body as below then click Send button.

{ "email":"[email&nbsp;protected]", "password": "q1w2we3r4" }

You will see this response in the bottom panel of Postman.

{
&nbsp; &nbsp; "username": "[email&nbsp;protected]",
&nbsp; &nbsp; "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJpbmZvQGRqYW13YXJlLmNvbSIsInJvbGVzIjpbeyJpZCI6IjVjODBjNjIzYjIwMTkxNGIyYTY5N2U4ZCIsInJvbGUiOiJBRE1JTiJ9XSwiaWF0IjoxNTUxOTY0OTc3LCJleHAiOjE1NTE5Njg1Nzd9.j5CHZ_LCmeQtdxQeH9eluxVXcOsHPWV1p8WnBn0CULo"
}

Copy the token then back to the GET product. Add a header with the name Authorization and the value that paste from a token that gets by login with additional Bearer prefix (with space) as below.

Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJpbmZvQGRqYW13YXJlLmNvbSIsInJvbGVzIjpbeyJpZCI6IjVjODBjNjIzYjIwMTkxNGIyYTY5N2U4ZCIsInJvbGUiOiJBRE1JTiJ9XSwiaWF0IjoxNTUxOTY0OTc3LCJleHAiOjE1NTE5Njg1Nzd9.j5CHZ_LCmeQtdxQeH9eluxVXcOsHPWV1p8WnBn0CULo

You should see this response after clicking the Send button.

[
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "id": "5c80dc6cb20191520567b68a",
&nbsp; &nbsp; &nbsp; &nbsp; "prodName": "Dummy Product 1",
&nbsp; &nbsp; &nbsp; &nbsp; "prodDesc": "The Fresh Dummy Product in The world part 1",
&nbsp; &nbsp; &nbsp; &nbsp; "prodPrice": 100,
&nbsp; &nbsp; &nbsp; &nbsp; "prodImage": "https://dummyimage.com/600x400/000/fff"
&nbsp; &nbsp; }
]

You can test the POST product with the token in headers using the same way.

That it’s, the Securing RESTful API with Spring Boot, Security, and Data MongoDB tutorial. You can get the full source code from our GitHub.

Learn More

Build a Simple CRUD App with Spring Boot and Vue.js

Creating RESTful APIs with NodeJS and MongoDB Tutorial

MongoDB with Python Crash Course - Tutorial for Beginners

How to build RESTful APIs with ASP.NET Core

Understanding the basics of RESTful APIs

Developing RESTful APIs with Lumen (A PHP Micro-framework)

Java Programming Masterclass for Software Developers

Java In-Depth: Become a Complete Java Engineer!

JSP, Servlets and JDBC for Beginners: Build a Database App

JSP, Servlet, JSLT + Hibernate: A complete guide

Create Login and Registration in Your ASP.NET Core MVC App

Create Login and Registration in Your ASP.NET Core MVC App

This tutorial walks you through setting up login and registration with ASP.NET Core MVC. In this tutorial, you learned how to add authentication to your ASP.NET Core MVC app and allow users to register for a new account.

This tutorial walks you through setting up login and registration with ASP.NET Core MVC. In this tutorial, you learned how to add authentication to your ASP.NET Core MVC app and allow users to register for a new account.

User authentication and authorization are common features in web applications, but building these mechanics has the potential to take a lot of time. Doing so requires setting up persistent storage for user information (in some type of database) and paying keen attention to potential security issues around sensitive operations like hashing passwords, password reset workflows, etc. - weeks of development time begin to add up before we ever get to the functionality that delivers value to your users.

In this post, we’ll walk through how Okta simplifies this process for us and set up a simple integration for an ASP.NET Core MVC app using the Okta NuGet package. We’ll build functionality for users to register for new accounts and login with their Okta credentials.

Scaffold Your ASP.NET Project

To follow along with this tutorial start by creating a new app in the console:

mkdir MyOktaProject
cd MyOktaProject
dotnet new mvc


Configure User Registration

If you don’t already have one, you’ll need to create an Okta developer account. Doing so will give you a URL for your organization called an “Okta domain”. It will also allow you to create your login credentials for accessing the Okta dashboard.

Upon submission of this form, you will receive an email Okta with instructions to obtain your credentials and complete your registration.

Execute the following steps to configure Okta so that users can register themselves for an account.

  1. From the Administrative Dashboard, hover over Users and click Registration
  2. Click Enable Registration
  3. Save the changes

Configure Basic User Authentication

Once you have access to your account you can proceed to the Dashboard using a link like the one below:

<span

class="okta-preview-domain">https://{yourOktaDomain}/admin/dashboard

On the Dashboard, click Applications in the main menu and on the Application screen, click Add Application.

Select Web then click Next.

On the Create New Application screen, set the following values:

Click Done, then click Edit next to General Settings on your newly created Okta app. Edit the following values:

Logout redirect URIs: https://localhost:5001/signout-callback-oidc

Initiate login URI: https://localhost:5001/authorization-code/callback

Add .NET Authentication Dependencies

Once your account is set up you need to add the Okta.Sdk library to your project. This post will take the approach of using the NuGet package, but the Github repository for Okta.AspNetCore can be found here.

To proceed simply search for the latest version of the Okta.Sdk NuGet package in your IDE of choice (version 1.2.0 at the time of this publication) and install it. If you’re using Visual Studio you can do this by right-clicking on the project in the solution explorer and selecting Manage NuGet Packages. For those of you not using Visual Studio, add the package via console window using the following command:

dotnet add package Okta.Sdk --version 1.2.0


Configure Your ASP.NET App for Login

Authentication works by redirecting users to the Okta website, where they will log in with their credentials, and then be returned to your site via the URL you configured above.

Add the following code to your appsettings.json file:

  "Okta": {
    "Issuer": "https://{yourOktaDomain}/oauth2/default",
    "ClientId": "{yourClientId}",
    "ClientSecret": "{yourClientSecret}"
  }


You can find each of the actual values needed to replace the settings in the config above in the following places:

ClientId refers to the client ID of the Okta application ClientSecret refers to the client secret of the Okta application Issuer will need the text {yourOktaDomain} replaced with your Okta domain, found at the top-right of the Dashboard page

Add some using statements to your Startup.cs file:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;


Add the following code to the top of the ConfigureServices method in your Startup.cs file:

services.AddAuthentication(sharedOptions =>
{
    sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.ClientId = Configuration["okta:ClientId"];
        options.ClientSecret = Configuration["okta:ClientSecret"];
        options.Authority = Configuration["okta:Issuer"];
        options.CallbackPath = "/authorization-code/callback";
        options.ResponseType = "code";
        options.SaveTokens = true;
        options.UseTokenLifetime = false;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name"
        };
    });


In the Configure() method of your Startup.cs file add this line just before the app.UseMvc() method:

app.UseAuthentication();


Add the following MeViewModel to the Models directory:

using System.Collections.Generic;

namespace OktaAspNetCoreMvc.Models
{
    public class MeViewModel
    {
        public string Username { get; set; }

        public bool SdkAvailable { get; set; }

        public dynamic UserInfo { get; set; }

        public IEnumerable<string> Groups { get; set; }
    }
}


Add Login to Your ASP.NET App

Now that all the configuration and plumbing is done, you’re ready to add the code that will actually log users into your application.

Add the following AccountController to the Controller directory.

The controller exposes the Login() action. If the user has already been authenticated, the Login() action will redirect them to the home page. Otherwise, it will redirect them to the Okta login screen.

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Okta.Sdk;

namespace OktaAspNetCoreMvc.Controllers
{
    public class AccountController : Controller
    {
        private readonly IOktaClient _oktaClient;

        public AccountController(IOktaClient oktaClient = null)
        {
            _oktaClient = oktaClient;
        }

        public IActionResult Login()
        {
            if (!HttpContext.User.Identity.IsAuthenticated)
            {
                return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
            }

            return RedirectToAction("Index", "Home");
        }
    }
}


Add the following code to your _Layout.cshtml file, just below the main menu to add the login button, or a welcome message, based on the current user’s status.:

   @if (User.Identity.IsAuthenticated)
    {
        <ul class="nav navbar-nav navbar-right">
            <li><p class="navbar-text">Hello, @User.Identity.Name</p></li>
        </ul>
    }
    else
    {
        <ul class="nav navbar-nav navbar-right">
            <li><a asp-controller="Account" asp-action="Login">Log in</a></li>
        </ul>
    }


For information on user authorization using Okta groups check out Lee Brandt’s article on user authorization in ASP.NET Core with Okta.

Register Users

If you following the instructions above to enable self-service registration the “Don’t have an account? Sign Up” message will appear at the bottom of the login form. In the next step, you’ll run the application.

Log In Using ASP.NET

That’s it! To run your solution open up a terminal and enter the following command:

dotnet run


Then navigate to http://localhost:5001 in your browser and enjoy!

The source code for this tutorial is available on GitHub.

Now you have a website with a working login and user registration form. Your website also allows users to recover lost passwords. By repeating these steps you can create a network of tools that your users can access all with the same login.

Learn More

The Complete ASP.NET MVC 5 Course

Build a Real-world App with ASP.NET Core and Angular 2 (4+)

ASP NET Core (ASP.NET 5),MVC 6,C#,Angular2 & EF Crash Course

Rest Api’s in Asp.Net and C#

Hands on ASP .Net Core 2

MEAN Stack Angular 8 CRUD Web Application

MEAN Stack Angular 8 CRUD Web Application

In this tutorial, you’ll learn Angular 8 MEAN Stack tutorial and how to build an Angular 8 CRUD web application from MEAN scratch with MongoDB, Express js, Node js, and Angular Material UI library.

In this tutorial, you’ll learn Angular 8 MEAN Stack tutorial and how to build an Angular 8 CRUD web application from MEAN scratch with MongoDB, Express js, Node js, and Angular Material UI library.

In this MEAN stack tutorial, you’ll learn to set up a MEAN stack project from scratch. I’ll be creating back-end and front-end for a real-world CRUD web application from scratch.

For the demo purpose, I’ll create a students record management CRUD (create, read, update & delete) web application. In this CRUD app user will be able to perform the following tasks:

  • Add student ID
  • Add student name
  • Add student email
  • Add section Angular Material dropdown
  • Add multiple subjects using Angular material input chips
  • Add student’s gender using Angular material radio buttons
  • Add student date of birth using Angular material datepicker
Angular 8 Project Setup
  • Setting up Node js
  • Setting up Angular 8 CLI
  • Installing & setting up Angular 8 project
  • Creating routes to navigate between components
  • Creating Angular 8 service to manage CRUD operations
  • Consuming RESTful APIsusing Angular 8 Service
Angular Material UI Library
  • Setting up an Angular material ui library in a real-world Angular application.
  • Creating web application’s front-end using Angular material ui components like :- Angular material default theme, icons, buttons, navbar, date-picker, form, data tables and chip inputs.
MEAN Stack Back-end Setup
  • Set up MongoDB in Angular 8 MEAN stack app.
  • Setting up Expressjs server with Node js.
  • Creating RESTful APIs with Node js and Express js.

Table of Contents

  1. Angular 8 MEAN stack tutorial – Workflow of MEAN Stack Angular Material tutorial
  2. Angular 8 MEAN stack tutorial – Installing Node JS and Angular CLI
  3. Angular 8 MEAN stack tutorial – Angular 8 web app project setup
  4. Angular 8 MEAN stack tutorial – Setting up Angular 8 Routes to navigate between components.
  5. Angular 8 MEAN stack tutorial – Setting up Angular Material UI Library in Angular project.
  6. Angular 8 MEAN stack tutorial – Build Mean Stack Backend with MongoDB, Node JS and Express JS.
  7. Angular 8 MEAN stack tutorial – Build Angular 8 Service to Consume REST APIs.
  8. Angular 8 MEAN stack tutorial – Add Student using MEAN Stack REST APIs with Angular Material.
  9. Angular 8 MEAN stack tutorial – Show Students List and Delete Student Object.
  10. Angular 8 MEAN stack tutorial – Edit Students Object.
#1 Workflow of MEAN Stack Angular Material Tutorial

I’ll create application’s frontend in Angular 8 using Angular material 8 UI components and backend with Node js, Expressjs and MongoDB. To make it developer friendly I’ll create a separate project for frontend and backend. I will be building RESTful API using MEAN stack backend and will use those APIs with Angular service to consume the data.

Following technologies, will be used throughout the tutorial.

  • NPM v6.4.1
  • Node v10.15.3
  • RxJS V6.5.2
  • Angular v8.0.0
  • AngularCLI v8.0.0
  • MongoDB 4.0.6
  • MongoDB shell v4.0.6
#2 Installing Node JS and Angular CLI

Firstly, you need to have Node.js and Angular CLI installed in your system to work with Angular 8 Mean stack project. To install Node.js in your system

Node.js will help us to install the required dependencies for this Mean stack project.

In the next step, we’ll be installing Angular CLI with the help of NPM. Now with the help of Angular CLI, we’ll install the new Mean stack project.

npm install @angular/cli -g

We’ve successfully installed Node.js and Angular CLI by now. Now we can use the ng command to generate new Angular project, components, services, routing or many more features of Angular 8.

#3 Angular 8 web app project setup

We are going to build a MEAN stack web app using Angular 8. In our MEAN stack web app, we’ll use the Angular 8 framework to create the frontend of the app. Run the below command to generate a new angular project.

ng new angular8-meanstack-angular-material

Answer some Angular CLI questions:

# ? Would you like to add Angular routing? = Yes

# ? Which stylesheet format would you like to use? = CSS

Head over to the newly created Angular 8 project’s directory using below cmd.

cd angular8-meanstack-angular-material

In next step we’ll create three new components to manage Angular 8 Mean stack Angular CRUD app. Use Angular 8 CLI to generate Angular 8 components:

ng g component components/add-student --module app
ng g component components/edit-student --module app
ng g component components/students-list --module app

We are using --module app parameter because we have 2 module files in the app folder. Now with the –module app parameter We are telling Angular CLI that app.module.ts is our main app module file.

#4 Setting up Angular 8 Routes to navigate between components.

In this part of the tutorial we’ll create routes in our Mean stack angular 8 CRUD app. Routes allow us to navigate between components in Angular app.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AddStudentComponent } from './components/add-student/add-student.component';
import { EditStudentComponent } from './components/edit-student/edit-student.component';
import { StudentsListComponent } from './components/students-list/students-list.component';
const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'add-student' },
{ path: 'add-student', component: AddStudentComponent },
{ path: 'edit-student/:id', component: EditStudentComponent },
{ path: 'students-list', component: StudentsListComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
#5 Setting up Angular Material UI Library in Angular project

We’ll be using Angular Material UI library to build students record management system. I will help you to create a beautiful responsive layout with Angular material ui components. We’ll create Mean stack Angular 8 CRUD app with following Angular material UI components:

  • Angular material default theme
  • Angular material datepicker
  • Angular material icons
  • Angular material buttons
  • Angular material navbar
  • Angular material form
  • Angular material data tables
  • Angular material chip inputs

Run the following command to setup Angular material.

ng add @angular/material

Choose the Angular material theme as per your choice:

? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink
❯ Indigo/Pink        [ Preview: https://material.angular.io?theme=indigo-pink ] 
Deep Purple/Amber  [ Preview: https://material.angular.io?theme=deeppurple-amber ] 
Pink/Blue Grey     [ Preview: https://material.angular.io?theme=pink-bluegrey ] 
Purple/Green       [ Preview: https://material.angular.io?theme=purple-green ]

Then it will ask for Hammer.js (Gesture recognition support) and Angular browser animation support.

Select yes and hit enter.

Set up HammerJS for gesture recognition? (Y/n) = Y
? Set up browser animations for Angular Material? (Y/n) = Y

We’ve installed Angular material UI library in Angular 8 Mean stack project. Now we’ll create a separate material.module.ts file. In this file we’ll import the various Angular material service so that we can use it and manage centrally in our Angular 8 CRUD web app.

In next step we’ll create a custom Angular material module, Create src > app > material.module.ts file and import the following Angular material UI components in this file like given below.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
MatButtonModule,
MatToolbarModule,
MatIconModule,
MatBadgeModule,
MatSidenavModule,
MatListModule,
MatGridListModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatRadioModule,
MatDatepickerModule,
MatNativeDateModule,
MatChipsModule,
MatTooltipModule,
MatTableModule,
MatPaginatorModule
} from '@angular/material';
@NgModule({
imports: [
CommonModule,
MatButtonModule,
MatToolbarModule,
MatIconModule,
MatSidenavModule,
MatBadgeModule,
MatListModule,
MatGridListModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatRadioModule,
MatDatepickerModule,
MatNativeDateModule,
MatChipsModule,
MatTooltipModule,
MatTableModule,
MatPaginatorModule
],
exports: [
MatButtonModule,
MatToolbarModule,
MatIconModule,
MatSidenavModule,
MatBadgeModule,
MatListModule,
MatGridListModule,
MatInputModule,
MatFormFieldModule,
MatSelectModule,
MatRadioModule,
MatDatepickerModule,
MatChipsModule,
MatTooltipModule,
MatTableModule,
MatPaginatorModule
],
providers: [
MatDatepickerModule,
]
})
export class AngularMaterialModule { }

Go to app.module.ts file and import the AngularMaterialModule.

/* Angular material */
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AngularMaterialModule } from './material.module';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
declarations: [...],
imports: [
BrowserAnimationsModule,
AngularMaterialModule,
],
providers: [...],
bootstrap: [...],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

Setup a basic layout with Angular Material

Go to app.component.html file and include the following code.



Student Records


menu




<mat-sidenav #sidenav [mode]="isBiggerScreen() ? 'over' : 'side'" [(opened)]="opened" [fixedInViewport]="true"
[fixedTopGap]>


add Add Student


format_list_bulleted View Students









Add the following code in app.component.ts file.

import { Component, ViewChild, HostListener, OnInit } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
opened = true;
@ViewChild('sidenav') sidenav: MatSidenav;
ngOnInit() {
console.log(window.innerWidth)
if (window.innerWidth < 768) {
this.sidenav.fixedTopGap = 55;
this.opened = false;
} else {
this.sidenav.fixedTopGap = 55;
this.opened = true;
}
}
@HostListener('window:resize', ['$event'])
onResize(event) {
if (event.target.innerWidth < 768) {
this.sidenav.fixedTopGap = 55;
this.opened = false;
} else {
this.sidenav.fixedTopGap = 55
this.opened = true;
}
}
isBiggerScreen() {
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
if (width < 768) {
return true;
} else {
return false;
}
}
}

To set up the style add the following code in styles.css file.

html,body{height:100%;}
body{margin:0;font-family:'Roboto', sans-serif;}
.header{justify-content:space-between;}
.user-profile{margin-left:15px;}
.mat-sidenav-container{height:100%;display:flex;flex:1 1 auto;}
.mat-nav-list .mat-list-item{font-size:15px;}
.nav-tool-items{display:inline-block;margin-right:13px;}
.user-profile{margin-left:15px;cursor:pointer;}
.hamburger{visibility:hidden !important;}
.mat-sidenav,.mat-sidenav-content{padding:15px;}
.mat-list-item.active{background:rgba(0, 0, 0, .04);}
.mat-sidenav-content{padding:25px 40px 0;}
.mat-sidenav{background-color:#F2F2F2;width:250px;}
.header{position:sticky;position:-webkit-sticky;top:0;z-index:1000;}
mat-sidenav mat-icon{margin-right:12px;}
.hamburger{margin-top:5px;cursor:pointer;}
.mat-radio-button,.mat-radio-group{margin-right:25px;}
.controlers-wrapper>*{width:100%;padding:0;}
.misc-bottom-padding{margin:8px 0 10px;}
.misc-bottom-padding mat-label{margin-right:15px;}
mat-radio-group mat-radio-button{margin-left:5px;}
.button-wrapper button{margin-right:5px;}
table.mat-table,table{width:100%;}
.inner-wrapper{padding:15px 0 130px;width:100%;}
.inner-wrapper mat-card{display:inline-block;margin:0 6% 0 0;vertical-align:top;width:44%;}
.full-wrapper{width:100%;}
.multiple-items{position:relative;}
.multiple-items .tooltip-info{right:0;top:7px;cursor:pointer;color:#a1a7c7;position:absolute;font-size:20px;}
body .push-right{margin-right:10px;}
.no-data{text-align:center;padding-top:30px;color:#6c75a9;}
.button-wrapper{margin:20px 0 0 0;}
@media (max-width:1024px){.inner-wrapper mat-card{width:100%;}
.mat-sidenav-content{padding:20px 20px 0;}
.misc-bottom-padding mat-label{display:block;padding-bottom:10px;}
.mat-sidenav{width:230px;}
.mat-nav-list .mat-list-item{font-size:14px;}
}
@media (max-width:767px){.nav-tool-items{margin-right:0;}
.hamburger{visibility:visible !important;}
}

Your basic layout is ready ready with Angular material library, in next step we’ll set up backend using node js, express js and mongoDB.

#6 Build Mean Stack Backend with MongoDB, NodeJS and ExpressJS

In this part of the tutorial, we are going to build a robust Mean stack backend using mongoDB, nodejs, and expressjs.

Following topics will be covered in this part of the tutorial:

  • Create a separate project for Angular 8 Mean stack backend.
  • Install required dependencies using NPM: body-parser, cors, express js, mongoose, and nodemon.
  • Set up MongoDB Database connection in Mean stack app to access MongoDB database using MongoDB Shell.
  • Define a data model with mongoose JS in Mean stack project.
  • Create RESTful APIs with Express js Routes in Mean Stack Angular 8 Project.
  • Configure Angular 8 Mean Stack backend

Create a separate project for Angular 8 Mean stack backend.

In order to set up a separate Mean stack backend create a folder by the name of backend in the Angular’s root directory.

mkdir backend && cd backend

You’ve created the backend folder and entered into the project.

Next thing is to create a separate package.json for your Mean stack backend.

npm init

Install required dependencies using NPM: body-parser, cors, express js, mongoose, and nodemon.

After that install the required dependencies for your Angular 8 Mean stack app.

npm install --save express mongoose cors body-parser

Then install nodemon package it will save us from restarting the server every-time we make the changes in our backend code.

npm install nodemon --save-dev

Your package.json file for Angular 8 Mean stack backend will look something like this.

{
"name": "angular8-meanstack-backend",
"version": "1.0.0",
"description": "An angular 8 mean stack crud web app with angular material 8.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Digamber Rawat",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"mongoose": "^5.5.11"
},
"devDependencies": {
"nodemon": "^1.19.1"
}
}

Set up MongoDB Database connection in Mean stack app to access MongoDB database using MongoDB Shell.

To setup the MongoDB database connection within the Mean stack app, we need to create a folder by the name of database and create a file db.js there. Run the given below command.

mkdir database && cd database && touch db.js

Inside the backend > database > db.js file paste the following code. Here angular8mean is your mongoDB database name.

module.exports = {
db: 'mongodb://localhost:27017/angular8mean'
};

Define Student data model with mongoose JS in Mean stack app.

We’ll create a model folder, inside the model folder we’ll create a Student Schema for students collection in MongoDB. Paste the below code in the model > Student.js file.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Define collection and schema
let Student = new Schema({
student_name: {
type: String
},
student_email: {
type: String
},
section: {
type: String
},
subjects: {
type: Array
},
gender: {
type: String
},
dob: {
type: Date
}
}, {
collection: 'students'
})
module.exports = mongoose.model('Student', Student)

Create RESTful APIs with Express js Routes in Mean Stack Angular 8 Project.

In this Angular 8 Mean stack tutorial we are going to create RESTful APIs using Express js and Node js. I will create a routes folder inside the backend folder and create a student.routes.js file.

Enter the below command to create the routes folder and student.routes.js file.

mkdir routes && cd routes && touch student.route.js

We’ve created RESTful APIs using Express js and Student Model, now Go to student.route.js file and add the following code.

const express = require('express');
const app = express();
const studentRoute = express.Router();
// Student model
let Student = require('../model/Student');
// Add Student
studentRoute.route('/add-student').post((req, res, next) => {
Student.create(req.body, (error, data) => {
if (error) {
return next(error)
} else {
res.json(data)
}
})
});
// Get all student
studentRoute.route('/').get((req, res) => {
Student.find((error, data) => {
if (error) {
return next(error)
} else {
res.json(data)
}
})
})
// Get single student
studentRoute.route('/read-student/:id').get((req, res) => {
Student.findById(req.params.id, (error, data) => {
if (error) {
return next(error)
} else {
res.json(data)
}
})
})
// Update student
studentRoute.route('/update-student/:id').put((req, res, next) => {
Student.findByIdAndUpdate(req.params.id, {
$set: req.body
}, (error, data) => {
if (error) {
return next(error);
console.log(error)
} else {
res.json(data)
console.log('Student successfully updated!')
}
})
})
// Delete student
studentRoute.route('/delete-student/:id').delete((req, res, next) => {
Student.findByIdAndRemove(req.params.id, (error, data) => {
if (error) {
return next(error);
} else {
res.status(200).json({
msg: data
})
}
})
})
module.exports = studentRoute;

Configure Angular 8 Mean Stack backend

Now we’ll create app.js file in backend folder’s root. Run the below command to generate backend > app.js file.

touch app.js

Mange Backend settings in Mean stack Project.

Now we are going to create app.js file this file will hold the core logic of our Mean stack project’s backend logic. This file will manage the following things.

  • Setup port using express.
  • Setup 404 error using express js.
  • Making mongoDB database connection
  • Serving static files using express js in Mean stack app.
  • Handling errors using Express js in Angular 8 Mean stack project.
let express = require('express'),
path = require('path'),
mongoose = require('mongoose'),
cors = require('cors'),
bodyParser = require('body-parser'),
dataBaseConfig = require('./database/db');
// Connecting mongoDB
mongoose.Promise = global.Promise;
mongoose.connect(dataBaseConfig.db, {
useNewUrlParser: true
}).then(() => {
console.log('Database connected sucessfully ')
},
error => {
console.log('Could not connected to database : ' + error)
}
)
// Set up express js port
const studentRoute = require('../backend/routes/student.route')
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(cors());
app.use(express.static(path.join(__dirname, 'dist/angular8-meanstack-angular-material')));
app.use('/', express.static(path.join(__dirname, 'dist/angular8-meanstack-angular-material')));
app.use('/api', studentRoute)
// Create port
const port = process.env.PORT || 4000;
const server = app.listen(port, () => {
console.log('Connected to port ' + port)
})
// Find 404 and hand over to error handler
app.use((req, res, next) => {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
console.error(err.message);
if (!err.statusCode) err.statusCode = 500;
res.status(err.statusCode).send(err.message);
});

Everything has been placed at its place, now we have to start the Angular 8 project, mongoDB server and Nodemon server.

Run the following commands…

Start the Angular project:

ng serve

Initialise the mongoDB database:

cd backend && mongod

Start the nodemon server:

cd backend && nodemon

I hope your Angular 8 Mean stack backend server is running fine, you can check your frontend and backend on the following URLs:

Angular frontend URL:

http://localhost:4200

MEAN stack backend URL:

http://localhost:4000/api

MEAN stack RESTful APIs using Express JS

We can hit the below command in the terminal to check out how our newly created RESTful APIs are working.

curl -i -H "Accept: application/json" localhost:4000/api
# HTTP/1.1 200 OK
# X-Powered-By: Express
# Access-Control-Allow-Origin: *
# Content-Type: application/json; charset=utf-8
# Content-Length: 58
# ETag: W/"3a-dzxOuKmgt3HAevjaPlycYSK+FhI"
# Date: Sun, 26 May 2019 18:53:03 GMT
# Connection: keep-alive

If we are getting this type of response that means we are ready to go with our APIs. Or similarly we can also use Postmen API development environment tool to test our RESTful APIs.

#7 Build Angular 8 Service to Consume REST APIs

To create Angular 8 Mean stack student records management system app. We need to create a service file where we’ll consume REST APIs to manage the student data. This service file will manage the Create, Read, Update and Delete operations.

Configure Angular 8 HttpClientModule:

Import HttpClientModule service in app.module.ts file.

/* Angular 8 http service */
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
HttpClientModule
]
})

Create & configure Student class:

Enter the below command to create model > student.ts file.

export class Student {
_id: String;
student_name: String;
student_email: String;
section: String;
subjects: Array;
dob: Date;
gender: String;
}
Create Angular 8 service to Consume REST APIs

Enter the following command to create Angular 8 service to manage CRUD operations in Angular 8 MEAN Stack web app.

ng g s shared/api

In the given below code we’ve consumed REST APIs using Angular 8 service. Add the following code in your shared > api.service.ts file.

import { Injectable } from '@angular/core';
import { Student } from './student';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ApiService {
endpoint: string = 'http://localhost:4000/api';
headers = new HttpHeaders().set('Content-Type', 'application/json');
constructor(private http: HttpClient) { }
// Add student
AddStudent(data: Student): Observable {
let API_URL = `${this.endpoint}/add-student`;
return this.http.post(API_URL, data)
.pipe(
catchError(this.errorMgmt)
)
}
// Get all students
GetStudents() {
return this.http.get(`${this.endpoint}`);
}
// Get student
GetStudent(id): Observable {
let API_URL = `${this.endpoint}/read-student/${id}`;
return this.http.get(API_URL, { headers: this.headers }).pipe(
map((res: Response) => {
return res || {}
}),
catchError(this.errorMgmt)
)
}
// Update student
UpdateStudent(id, data: Student): Observable {
let API_URL = `${this.endpoint}/update/${id}`;
return this.http.put(API_URL, data, { headers: this.headers }).pipe(
catchError(this.errorMgmt)
)
}
// Delete student
DeleteStudent(id): Observable {
var API_URL = `${this.endpoint}/delete-student/${id}`;
return this.http.delete(API_URL).pipe(
catchError(this.errorMgmt)
)
}
// Error handling 
errorMgmt(error: HttpErrorResponse) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Get client-side error
errorMessage = error.error.message;
} else {
// Get server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
console.log(errorMessage);
return throwError(errorMessage);
}
}

Go to app.module.ts file and import this API service like given below.

/* Angular 8 CRUD services */
import { ApiService } from './shared/api.service';
@NgModule({
providers: [ApiService]
})
#8 Add Student using MEAN Stack REST APIs with Angular Material

In this part of the tutorial we will learn to add student in the MongoDB database. We’ll be using Angular 8 Reactive form to add student in the database.

Import ReactiveFormsModule API in App Module File

In order to work with Angular 8 Reactive Forms we must import the ReactiveFormsModule API and FormsModule API in app.module.ts file.

/* Reactive form services in Angular 8 */
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
ReactiveFormsModule,
FormsModule
],
})
export class AppModule { }

Go to add-student.component.ts file and include the given below code.

import { Router } from '@angular/router';
import { Component, OnInit, ViewChild, NgZone } from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material';
import { ApiService } from './../../shared/api.service';
import { FormGroup, FormBuilder, Validators } from "@angular/forms";
export interface Subject {
name: string;
}
@Component({
selector: 'app-add-student',
templateUrl: './add-student.component.html',
styleUrls: ['./add-student.component.css']
})
export class AddStudentComponent implements OnInit {
visible = true;
selectable = true;
removable = true;
addOnBlur = true;
@ViewChild('chipList') chipList;
@ViewChild('resetStudentForm') myNgForm;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
studentForm: FormGroup;
subjectArray: Subject[] = [];
SectioinArray: any = ['A', 'B', 'C', 'D', 'E'];
ngOnInit() {
this.submitBookForm();
}
constructor(
public fb: FormBuilder,
private router: Router,
private ngZone: NgZone,
private studentApi: ApiService
) { }
/* Reactive book form */
submitBookForm() {
this.studentForm = this.fb.group({
student_name: ['', [Validators.required]],
student_email: ['', [Validators.required]],
section: ['', [Validators.required]],
subjects: [this.subjectArray],
dob: ['', [Validators.required]],
gender: ['Male']
})
}
/* Add dynamic languages */
add(event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
// Add language
if ((value || '').trim() && this.subjectArray.length < 5) {
this.subjectArray.push({ name: value.trim() })
}
// Reset the input value
if (input) {
input.value = '';
}
}
/* Remove dynamic languages */
remove(subject: Subject): void {
const index = this.subjectArray.indexOf(subject);
if (index >= 0) {
this.subjectArray.splice(index, 1);
}
}  
/* Date */
formatDate(e) {
var convertDate = new Date(e.target.value).toISOString().substring(0, 10);
this.studentForm.get('dob').setValue(convertDate, {
onlyself: true
})
}  
/* Get errors */
public handleError = (controlName: string, errorName: string) => {
return this.studentForm.controls[controlName].hasError(errorName);
}  
/* Submit book */
submitStudentForm() {
if (this.studentForm.valid) {
this.studentApi.AddStudent(this.studentForm.value).subscribe(res => {
this.ngZone.run(() => this.router.navigateByUrl('/students-list'))
});
}
}
}

Then go to add-student.component.html file and add the following code.



# Add Student













You must provide a**student name**






You must provide a**student email**




Section

{{sectioinArray}}



Section is required










<mat-chip *ngFor="let subjectArray of subjectArray" [selectable]="selectable" [removable]="removable"
(removed)="remove(subjectArray)">
{{subjectArray.name}}
cancel

<input placeholder="Add subject" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">


info




<input matInput readonly [matDatepicker]="picker" placeholder="Date of birth" formControlName="dob"
(dateChange)="formatDate($event)">



Date of birth is required




Gender:

Male
Female








Submit





#9 Show Students List and Delete Student Object

Go to students-list.component.ts file and add the given below code. In this file, we’ll manage the following tasks.

  • Implement the Angular material data tables and Pagination with Angular 8 Mean stack project.
  • Render Students List using Mean stack REST APIs
  • Delete Single Object using REST APIs in Angular 8 Mean stack app
import { Student } from './../../shared/student';
import { ApiService } from './../../shared/api.service';
import { Component, ViewChild, OnInit } from '@angular/core';
import { MatPaginator, MatTableDataSource } from '@angular/material';
@Component({
selector: 'app-students-list',
templateUrl: './students-list.component.html',
styleUrls: ['./students-list.component.css']
})
export class StudentsListComponent implements OnInit {
StudentData: any = [];
dataSource: MatTableDataSource;
@ViewChild(MatPaginator) paginator: MatPaginator;
displayedColumns: string[] = ['_id', 'student_name', 'student_email', 'section', 'action'];
constructor(private studentApi: ApiService) {
this.studentApi.GetStudents().subscribe(data => {
this.StudentData = data;
this.dataSource = new MatTableDataSource(this.StudentData);
setTimeout(() => {
this.dataSource.paginator = this.paginator;
}, 0);
})    
}
ngOnInit() { }
deleteStudent(index: number, e){
if(window.confirm('Are you sure')) {
const data = this.dataSource.data;
data.splice((this.paginator.pageIndex * this.paginator.pageSize) + index, 1);
this.dataSource.data = data;
this.studentApi.DeleteStudent(e._id).subscribe()
}
}
}

Now, go to students-list.component.html file and include the following code.



# Students List




There is no student added yet!

 0">



 Student ID 
 {{element._id}} 


 Student Name 
 {{element.student_name}} 


 Email 
 {{element.student_email}} 


 Section 
 {{element.section}} 


 Action 

<button mat-raised-button color="primary" class="push-right"
[routerLink]="['/edit-student/', element._id]">Edit
Delete








#10 Edit Students Object in Mean Stack App

We are going to create edit functionality using RESTful API in Mean stack app with Angular Material 8.

Go to edit-list.component.ts file and add the following code.

import { Router, ActivatedRoute } from '@angular/router';
import { Component, OnInit, ViewChild, NgZone } from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material';
import { ApiService } from './../../shared/api.service';
import { FormGroup, FormBuilder, Validators } from "@angular/forms";
export interface Subject {
name: string;
}
@Component({
selector: 'app-edit-student',
templateUrl: './edit-student.component.html',
styleUrls: ['./edit-student.component.css']
})
export class EditStudentComponent implements OnInit {
visible = true;
selectable = true;
removable = true;
addOnBlur = true;
@ViewChild('chipList') chipList;
@ViewChild('resetStudentForm') myNgForm;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
studentForm: FormGroup;
subjectArray: Subject[] = [];
SectioinArray: any = ['A', 'B', 'C', 'D', 'E'];
ngOnInit() {
this.updateBookForm();
}
constructor(
public fb: FormBuilder,
private router: Router,
private ngZone: NgZone,
private actRoute: ActivatedRoute,
private studentApi: ApiService
) { 
var id = this.actRoute.snapshot.paramMap.get('id');
this.studentApi.GetStudent(id).subscribe(data => {
console.log(data.subjects)
this.subjectArray = data.subjects;
this.studentForm = this.fb.group({
student_name: [data.student_name, [Validators.required]],
student_email: [data.student_email, [Validators.required]],
section: [data.section, [Validators.required]],
subjects: [data.subjects],
dob: [data.dob, [Validators.required]],
gender: [data.gender]
})      
})    
}
/* Reactive book form */
updateBookForm() {
this.studentForm = this.fb.group({
student_name: ['', [Validators.required]],
student_email: ['', [Validators.required]],
section: ['', [Validators.required]],
subjects: [this.subjectArray],
dob: ['', [Validators.required]],
gender: ['Male']
})
}
/* Add dynamic languages */
add(event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
// Add language
if ((value || '').trim() && this.subjectArray.length < 5) {
this.subjectArray.push({ name: value.trim() })
}
// Reset the input value
if (input) {
input.value = '';
}
}
/* Remove dynamic languages */
remove(subject: Subject): void {
const index = this.subjectArray.indexOf(subject);
if (index >= 0) {
this.subjectArray.splice(index, 1);
}
}
/* Date */
formatDate(e) {
var convertDate = new Date(e.target.value).toISOString().substring(0, 10);
this.studentForm.get('dob').setValue(convertDate, {
onlyself: true
})
}
/* Get errors */
public handleError = (controlName: string, errorName: string) => {
return this.studentForm.controls[controlName].hasError(errorName);
}
/* Update book */
updateStudentForm() {
console.log(this.studentForm.value)
var id = this.actRoute.snapshot.paramMap.get('id');
if (window.confirm('Are you sure you want to update?')) {
this.studentApi.UpdateStudent(id, this.studentForm.value).subscribe( res => {
this.ngZone.run(() => this.router.navigateByUrl('/students-list'))
});
}
}
}

Now go to edit-list.component.html file and add the following code.



# Add Student













You must provide a**student name**






You must provide a**student email**




Section

{{sectioinArray}}



Section is required










<mat-chip *ngFor="let subjectArray of subjectArray" [selectable]="selectable" [removable]="removable"
(removed)="remove(subjectArray)">
{{subjectArray.name}}
cancel

<input placeholder="Add subject" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">


info




<input matInput readonly [matDatepicker]="picker" placeholder="Date of birth" formControlName="dob"
(dateChange)="formatDate($event)">



Date of birth is required




Gender:

Male
Female








Update





Conclusion

Finally, we have created a basic Angular 8 MEAN stack CRUD web app with Angular Material. Anyhow, if we have missed anything you can check out GitHub repo of this project.

How to build a CRUD Web App with Angular 8.0

How to build a CRUD Web App with Angular 8.0

In this Angular 8 tutorial you will learn how to build Angular 8 CRUD Web App as the frontend, and use existing Node, Express.js, and MongoDB RESTful API as the backend.

In this Angular 8 tutorial you will learn how to build Angular 8 CRUD Web App as the frontend, and use existing Node, Express.js, and MongoDB RESTful API as the backend.

Just clone and run the RESTful API backend here or you can use your existing backend/REST API with JSON format for this Angular 8 tutorial.

Table of Contents:
  • Install/Update Angular 8 CLI and Create a New Application
  • Create the Angular 8 Routes
  • Create an Angular 8 Service
  • Display List of Products using Angular 8 Material
  • Show and Delete Product Details using Angular 8 Material
  • Add a Product using Angular 8 Material
  • Edit a Product using Angular 8 Material
  • Build, Run, and Test the Angular 8 CRUD Web Application

We will not describe the new Angular 8 feature here because the official Angular.io blog has explained very well. As usual, we will show you a practical walkthrough from the zero to the complete application.

The following tools, frameworks, and modules are required for this tutorial:

We assume that you have installed Node.js. Now, we need to check the Node.js and NPM versions. Open the terminal or Node command line then type this commands.

node -v
v10.15.1
npm -v
6.9.0

That's the Node.js and NPM version that we are using. Now, you can go to the main steps.

Install/Update Angular 8 CLI and Create a New Application

If you are in an existing Angular 7 application, you can update the application using this command form your Angular 7 root directory.

cd angular7-crud
ng update @angular/cli @angular/core

Now, you will see the new version in dependencies in package.json except for @angular/material and @angular/cdk.

{
  ...
  "dependencies": {
    "@angular/animations": "~8.0.2",
    "@angular/cdk": "^7.0.0",
    "@angular/common": "~8.0.2",
    "@angular/compiler": "~8.0.2",
    "@angular/core": "~8.0.2",
    "@angular/forms": "~8.0.2",
    "@angular/material": "^7.0.0",
    "@angular/platform-browser": "~8.0.2",
    "@angular/platform-browser-dynamic": "~8.0.2",
    "@angular/router": "~8.0.2",
    "core-js": "^2.5.4",
    "hammerjs": "^2.0.8",
    "rxjs": "~6.5.2",
    "tslib": "^1.9.0",
    "zone.js": "~0.9.1"
  },
  ...
}

To update Angular Material and CDK, first, you have to uninstall them manually.

npm uninstall --save @angular/material
npm uninstall --save @angular/cdk

Then install them again using Angular 8 CLI.

ng add @angular/material

Choose your default theme during installation progress.

? Choose a prebuilt theme name, or "custom" for a custom theme:
❯ Indigo/Pink        [ Preview: https://material.angular.io?theme=indigo-pink ]
  Deep Purple/Amber  [ Preview: https://material.angular.io?theme=deeppurple-amber ]
  Pink/Blue Grey     [ Preview: https://material.angular.io?theme=pink-bluegrey ]
  Purple/Green       [ Preview: https://material.angular.io?theme=purple-green ]
  Custom

Leave the other question as default by type Y. Now if you check the package.json the Angular Material and CDK version updated to 8.0.1. Next, we have to run the updated Angular 8 application after running the MongoDB and Node/Express.js API.

ng serve

Oops, there's something wrong with the CSS.

ERROR in ./src/app/product-add/product-add.component.scss
Module build failed (from ./node_modules/sass-loader/lib/loader.js):

.example-full-width:nth-last-child() {
                                  ^
      Expected "n".
   ╷
17 │ .example-full-width:nth-last-child(){
   │                                    ^
   ╵
  stdin 17:36  root stylesheet

Just change all SCSS code that contains nth-last-child() with this.

nth-last-child(0)

You can find the reference about this here

https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-child. Now, you can see the updated Angular 8 application performance in the browser by going to [http://localhost:4200](http://localhost:4200`) and feel the performance difference with the previous version.

Now, for the new Angular 8 just type this Angular 8 CLI command.

ng new angular8-crud

If you get the question like below, choose Yes and SCSS (or whatever you like to choose).

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS

Next, go to the newly created Angular 8 project folder.

cd angular8-crud

Type this command to run the Angular 8 application using this command.

ng serve

Open your browser then go to this address localhost:4200, you should see this Angular 8 page.

Create the Angular 8 Routes

The Angular 8 routes already added when we create new Angular 8 application in the previous step. Routes use to navigate between components. Before configuring the routes, type this command to create a new Angular 8 components.

ng g component products
ng g component product-detail
ng g component product-add
ng g component product-edit

Open src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add these imports.

import { ProductsComponent } from './products/products.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
import { ProductAddComponent } from './product-add/product-add.component';
import { ProductEditComponent } from './product-edit/product-edit.component';

Add these arrays to the existing routes constant.

const routes: Routes = [
  {
    path: 'products',
    component: ProductsComponent,
    data: { title: 'List of Products' }
  },
  {
    path: 'product-details/:id',
    component: ProductDetailComponent,
    data: { title: 'Product Details' }
  },
  {
    path: 'product-add',
    component: ProductAddComponent,
    data: { title: 'Add Product' }
  },
  {
    path: 'product-edit/:id',
    component: ProductEditComponent,
    data: { title: 'Edit Product' }
  },
  { path: '',
    redirectTo: '/products',
    pathMatch: 'full'
  }
];

Open and edit src/app/app.component.html and you will see existing router outlet. Next, modify this HTML page to fit the CRUD page.


  ![](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==)



  

Open and edit src/app/app.component.scss then replace all SASS codes with this.

.container {
  padding: 20px;
}
Create an Angular 8 Service

To access RESTful API from Angular 8, we need to create an Angular 8 service which will handle all POST, GET, UPDATE, DELETE requests. The response from the RESTful API emitted by Observable that can subscribe and read from the Components. Before creating a service for RESTful API access, first, we have to install or register HttpClientModule. Open and edit src/app/app.module.ts then add this import.

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add it to @NgModule imports after BrowserModule.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule
],

We will use type specifier to get a typed result object. For that, create a new Typescript file src/app/product.ts then add these lines of Typescript codes.

export class Product {
  _id: string;
  prod_name: string;
  prod_desc: string;
  prod_price: number;
  updated_at: Date;
}

Next, generate an Angular 8 service by typing this command.

ng g service api

Next, open and edit src/app/api.service.ts then add these imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Product } from './product';

Add these constants before the @Injectable.

const httpOptions = {
  headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = "/api/v1/products";

Inject HttpClient module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

private handleError (operation = 'operation', result?: T) {
  return (error: any): Observable => {

    // TODO: send the error to remote logging infrastructure
    console.error(error); // log to console instead

    // Let the app keep running by returning an empty result.
    return of(result as T);
  };
}

Add the functions for all CRUD (create, read, update, delete) RESTful call of products data.

getProducts(): Observable {
  return this.http.get(apiUrl)
    .pipe(
      tap(product => console.log('fetched products')),
      catchError(this.handleError('getProducts', []))
    );
}

getProduct(id: number): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.get(url).pipe(
    tap(_ => console.log(`fetched product id=${id}`)),
    catchError(this.handleError(`getProduct id=${id}`))
  );
}

addProduct(product: Product): Observable {
  return this.http.post(apiUrl, product, httpOptions).pipe(
    tap((prod: Product) => console.log(`added product w/ id=${product.id}`)),
    catchError(this.handleError('addProduct'))
  );
}

updateProduct(id: any, product: Product): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.put(url, product, httpOptions).pipe(
    tap(_ => console.log(`updated product id=${id}`)),
    catchError(this.handleError('updateProduct'))
  );
}

deleteProduct(id: any): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.delete(url, httpOptions).pipe(
    tap(_ => console.log(`deleted product id=${id}`)),
    catchError(this.handleError('deleteProduct'))
  );
}

You can find more details about Angular 8 Observable and RXJS here.

Display List of Products using Angular 8 Material

We will display the list of products published from API Service. The data published from the API service read by subscribing as a Product model in the Angular 8 component. For that, open and edit src/app/products/products.component.ts then add these imports.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Next, for the user interface (UI) we will use Angular 8 Material and CDK. There's a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 8 Material.

ng add @angular/material

If there are questions like below, just use the default answer.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green       [ Preview: h
ttps://material.angular.io?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

We will register all required Angular 8 Material components or modules to src/app/app.module.ts. Open and edit that file then add these imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";

Also, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, back to src/app/products/products.component.ts then add these imports.

import { Product } from '../product';

Declare the variables of Angular 8 Material Table Data Source before the constructor.

displayedColumns: string[] = ['prod_name', 'prod_price'];
data: Product[] = [];
isLoadingResults = true;

Modify the ngOnInit function to get list of products immediately.

ngOnInit() {
  this.api.getProducts()
    .subscribe((res: any) => {
      this.data = res;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Next, open and edit src/app/products/products.component.html then replace all HTML tags with this Angular Material tags.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    add
  
  
    <table mat-table [dataSource]="data" class="example-table"
           matSort matSortActive="prod_name" matSortDisableClear matSortDirection="asc">

      
      
        Product Name
        {{row.prod_name}}
      

      
      
        Product Price
        $ {{row.prod_price}}
      

      
      
    
  

Finally, to make a little UI adjustment, open and edit src/app/products/products.component.scss then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-table-container {
  position: relative;
  max-height: 400px;
  overflow: auto;
}

table {
  width: 100%;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.example-rate-limit-reached {
  color: #980000;
  max-width: 360px;
  text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
  max-width: 64px;
}

.mat-column-created {
  max-width: 124px;
}

.mat-flat-button {
  margin: 5px;
}

If you don't want to use SASS for styling, rename the file extension to SCSS if the generated style file using SASS. Then change in the src/app/products/products.component.ts @Component declarations.

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.scss']
})
Show and Delete Product Details using Angular 8 Material

To show product details after click or tap on the one of a row inside the Angular 8 Material table, open and edit src/app/product-detail/product-detail.component.ts then add these imports.

import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Product } from '../product';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Declare the variables before the constructor for hold product data that get from the API.

product: Product = { _id: '', prod_name: '', prod_desc: '', prod_price: null, updated_at: null };
isLoadingResults = true;

Add a function for getting Product data from the API.

getProductDetails(id: any) {
  this.api.getProduct(id)
    .subscribe((data: any) => {
      this.product = data;
      console.log(this.product);
      this.isLoadingResults = false;
    });
}

Call that function when the component is initiated.

ngOnInit() {
  this.getProductDetails(this.route.snapshot.params['id']);
}

Add this function for delete product.

deleteProduct(id: any) {
  this.isLoadingResults = true;
  this.api.deleteProduct(id)
    .subscribe(res => {
        this.isLoadingResults = false;
        this.router.navigate(['/products']);
      }, (err) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

For the view, open and edit src/app/product-detail/product-detail.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    list
  
  
    
      ## {{product.prod_name}}

      {{product.prod_desc}}
    
    
      
        Product Price:
        {{product.prod_price}}
        Updated At:
        {{product.updated_at | date}}
      
    
    
      edit
      delete
    
  

Finally, open and edit src/app/product-detail/product-detail.component.scss then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}

If the style file extension is not SCSS, do the same way as previous steps.

Add a Product using Angular 8 Material

To create a form for adding a Product, open and edit src/app/product-add/product-add.component.ts then add these imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

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

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

productForm: FormGroup;
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;

Add initial validation for each field.

ngOnInit() {
  this.productForm = this.formBuilder.group({
    'prod_name' : [null, Validators.required],
    'prod_desc' : [null, Validators.required],
    'prod_price' : [null, Validators.required]
  });
}

Create a function for submitting or POST product form.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.addProduct(this.productForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/product-details', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      });
}

Next, add this import for implementing ErrorStateMatcher.

import { ErrorStateMatcher } from '@angular/material/core';

Create a new class before the main class @Components.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Instantiate that MyErrorStateMatcher as a variable in main class.

matcher = new MyErrorStateMatcher();

Next, open and edit src/app/product-add/product-add.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    list
  
  
    
      
        <input matInput placeholder="Product Name" formControlName="prod_name"
               [errorStateMatcher]="matcher">
        
          Please enter Product Name
        
      
      
        <input matInput placeholder="Product Desc" formControlName="prod_desc"
               [errorStateMatcher]="matcher">
        
          Please enter Product Description
        
      
      
        <input matInput placeholder="Product Price" formControlName="prod_price"
               [errorStateMatcher]="matcher">
        
          Please enter Product Price
        
      
      
        save
      
    
  

Finally, open and edit src/app/product-add/product-add.component.scss then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

If the style file extension is not SCSS, do the same way as previous steps.

Edit a Product using Angular 8 Material

We have put an edit button inside the Product Detail component for call Edit page. Now, open and edit src/app/product-edit/product-edit.component.ts then add these imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the product form before the constructor.

productForm: FormGroup;
_id = '';
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;

Next, add validation for all fields when the component is initiated.

ngOnInit() {
  this.getProduct(this.route.snapshot.params['id']);
  this.productForm = this.formBuilder.group({
    'prod_name' : [null, Validators.required],
    'prod_desc' : [null, Validators.required],
    'prod_price' : [null, Validators.required]
  });
}

Create a function for getting product data that filled to each form fields.

getProduct(id: any) {
  this.api.getProduct(id).subscribe((data: any) => {
    this._id = data._id;
    this.productForm.setValue({
      prod_name: data.prod_name,
      prod_desc: data.prod_desc,
      prod_price: data.prod_price
    });
  });
}

Create a function to update the product changes.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.updateProduct(this._id, this.productForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/product-details', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

Add a function for handling the show product details button.

productDetails() {
  this.router.navigate(['/product-details', this._id]);
}

Next, add this import for implementing ErrorStateMatcher.

import { ErrorStateMatcher } from '@angular/material/core';

Create a new class before the main class @Components.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Instantiate that MyErrorStateMatcher as a variable in main class.

matcher = new MyErrorStateMatcher();

Next, open and edit src/app/product-edit/product-edit.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    info
  
  
    
      
        <input matInput placeholder="Product Name" formControlName="prod_name"
               [errorStateMatcher]="matcher">
        
          Please enter Product Name
        
      
      
        <input matInput placeholder="Product Desc" formControlName="prod_desc"
               [errorStateMatcher]="matcher">
        
          Please enter Product Description
        
      
      
        <input matInput placeholder="Product Price" formControlName="prod_price"
               [errorStateMatcher]="matcher">
        
          Please enter Product Price
        
      
      
        save
      
    
  

Finally, open and edit src/app/product-edit/product-edit.component.scss then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

If the style file extension is not SCSS, do the same way as previous steps.

Build, Run, and Test the Angular 8 CRUD Web Application

Let's prove the performance of the Angular 8 CRUD Web Application. Now, we have to build the Angular 8 application using this command.

ng build --prod

And we have an 877KB size of the Angular 8 application build for production. Next, we have to test the whole application, first, we have to run MongoDB server and Node/Express API in the different terminal.

mongod
nodemon

Then run the Angular 8 application build, simply type this command.

ng serve

That it's the Angular 8 CRUD Web App. You can find the full source code in our GitHub.

How to Use a SQL Like and Regex Search in MongoDB and Node.JS

How to Use a SQL Like and Regex Search in MongoDB and Node.JS

In this article we will know how to use Regex to search in MongoDB like the SQL Like Statement

In this article we will know how to use Regex to search in MongoDB like the SQL Like Statement

To select the documents from a collection, you can use the db.collection.find() method. To select all documents in the collection, pass an empty document as the query filter document to the method.

In the shell, copy and paste the following to return all documents in the members collection.

db.members.find({})

To format the results, append the .pretty() to the find operation:

db.members.find({}).pretty()

Searching for Word Using Regex

Now that we are using .find() to query our collection, we can actually modify our syntax ever so slightly and begin searching for matches based on a word or phrase that may be a partial match within a given field, similar to the LIKE operator for SQL engines.

The trick is to utilize regular expressions (or regex for short), which is basically a text string that defines a search pattern. There are a number of regex engines that are written in slightly different syntax, but the fundamentals are all basically the same, and in this case, MongoDB uses the Perl Regex (PCRE) engine.

At the most basic level, a regex expression is a string (series of characters) enclosed on both sides by a single slash (/).

For example, if we want to use regex to perform the same query as above and find out how many members serve Neha, we can replace our string "Neha" with /Neha/ instead:

db.members.find( { name: /Neha/ } )

But imagine we want to find the number of restaurants where borough starts with the first three characters "Neha". We’d modify our regex very slightly, like so:

db.members.find( { name: /^Neha/ } )

The caret character (^) specifies the location in our string which should be the beginning, so if we had a document where those three letters were in the middle of the field, we wouldn’t get a match.

This informs the regex engine that we want to the search to be case insensitive, matching regardless of upper or lowercase. We also added the special i flag following our regex closing slash (/):

db.members.find( { name: /Neha/i } )

Using variable regex with MongoDB in Node.JS

var search ='Neha';

db.members.find(name: new RegExp(search)) //For substring search, case sensitive. 

db.members.find(name: new RegExp('^' + search + '$')) //For exact search, case sensitive

db.members.find(name: new RegExp(search, ‘i')) //For substring search, case insensitive

db.members.find(name: new RegExp('^' +search + '$', 'i')); //For exact search, case insensitive

Happy Coding!

Please like and share so I have the motivation to continue sharing!