How do you rename a MongoDB database?

There's a typo in my MongoDB database name and I'm looking to rename the database.

There's a typo in my MongoDB database name and I'm looking to rename the database.

I can copy and delete like so...

db.copyDatabase('old_name', 'new_name');
use old_name
db.dropDatabase();

Is there a command to rename a database?

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 {
    ext {
        springBootVersion = '2.1.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

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 {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    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) {
    super();
    this.prodName = prodName;
    this.prodDesc = prodDesc;
    this.prodPrice = prodPrice;
    this.prodImage = prodImage;
}

Generate or create Getter and Setter for each field.

public String getId() {
    return id;
}

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

public String getProdName() {
    return prodName;
}

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

public String getProdDesc() {
    return prodDesc;
}

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

public Double getProdPrice() {
    return prodPrice;
}

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

public String getProdImage() {
    return prodImage;
}

public void setProdImage(String prodImage) {
    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 {

    @Id
    private String id;
    @Indexed(unique = true, direction = IndexDirection.DESCENDING, dropDups = true)
    private String email;
    private String password;
    private String fullname;
    private boolean enabled;
    @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

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!

Typegoose - Define Mongoose models using TypeScript classes

Typegoose - Define Mongoose models using TypeScript classes

Typegoose - Define Mongoose models using TypeScript classes

Basic usage
import { prop, getModelForClass } from '@typegoose/typegoose';
import * as mongoose from 'mongoose';

class User {
  @prop()
  name?: string;
}

const UserModel = getModelForClass(User);

// UserModel is a regular Mongoose Model with correct types
(async () => {
  await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });

  const { _id: id } = await UserModel.create({ name: 'JohnDoe' });
  const user = await UserModel.findById(id).exec();

  console.log(user);
  // prints { _id: 59218f686409d670a97e53e0, name: 'JohnDoe', __v: 0 }
})();
Motivation

A common problem when using Mongoose with TypeScript is that you have to define both the Mongoose model and the TypeScript interface. If the model changes, you also have to keep the TypeScript interface file in sync or the TypeScript interface would not represent the real data structure of the model.

Typegoose aims to solve this problem by defining only a TypeScript interface (class) which need to be enhanced with special Typegoose decorators.

Under the hood it uses the Reflect & reflect-metadata API to retrieve the types of the properties, so redundancy can be significantly reduced.

Instead of:

interface Car {
  model?: string;
}

interface Job {
  title?: string;
  position?: string;
}

interface User {
  name?: string;
  age: number;
  job?: Job;
  car: Car | string;
}

mongoose.model('User', {
  name: String,
  age: { type: Number, required: true },
  job: {
    title: String;
    position: String;
  },
  car: { type: Schema.Types.ObjectId, ref: 'Car' }
});

mongoose.model('Car', {
  model: string,
});

You can just:

class Job {
  @prop()
  title?: string;

  @prop()
  position?: string;
}

class Car {
  @prop()
  model?: string;
}

class User {
  @prop()
  name?: string;

  @prop({ required: true })
  age!: number;

  @prop()
  job?: Job;

  @prop({ ref: Car })
  car?: Ref<Car>;
}

Please note that sub documents do not have to extend Typegoose. You can still give them default value in prop decorator, but you can't create static or instance methods on them.


Requirements
  • TypeScript 3.2+
  • Node 8+
  • mongoose ^5.7.1
  • emitDecoratorMetadata and experimentalDecorators must be enabled in tsconfig.json
Install

npm i -s @typegoose/typegoose

You also need to install mongoose, since version 5 it is listed as a peer-dependency

npm i -s mongoose

Migrate to 6.0.0 Testing

npm test

Versioning

Major.Minor.Fix (or how npm expresses it Major.Minor.Patch)
(This Project should comply with Semver)

API Documentation

Please use the new guides and the new docs this here is just for "legacy reasons" and might be deleted later

Typegoose class

Since 6.0.0 deprecated, please try to remove it

###Methods

getModelForClass<T>(cl: T)

This method returns the corresponding Mongoose Model for the class (T). If no Mongoose model exists for this class yet, one will be created automatically (by calling the method setModelForClass).

setModelForClass<T>(cl: T)

This Method is Deprecated see Migrate to 6.0.0

Property decorators

Typegoose comes with TypeScript decorators, which responsibility is to connect the Mongoose schema behind the TypeScript class.

prop(options)

The prop decorator adds the target class property to the Mongoose schema as a property. Typegoose checks the decorated property's type and sets the schema property accordingly. If another Typegoose extending class is given as the type, Typegoose will recognize this property as a sub document.

The options object accepts multiple config properties:

  • required: Just like the Mongoose required it accepts a handful of parameters. Please note that it's the developer's responsibility to make sure that if required is set to false then the class property should be optional.

    Note: for coding style (and type completion) you should use ! when it is marked as required

    // this is now required in the schema
    @prop({ required: true })
    firstName!: string;
    
    // by default, a property is not required
    @prop()
    lastName?: string; // using the ? optional property
    
  • index: Tells Mongoose whether to define an index for the property.

    @prop({ index: true })
    indexedField?: string;
    
  • unique: Just like the Mongoose unique, tells Mongoose to ensure a unique index is created for this path.

    // this field is now unique across the collection
    @prop({ unique: true })
    uniqueId?: string;
    
  • enum: The enum option accepts a string array. The class property which gets this decorator should have an enum-like type which values are from the provided string array. The way how the enum is created is delegated to the developer, Typegoose needs a string array which hold the enum values, and a TypeScript type which tells the possible values of the enum. However, if you use TS 2.4+, you can use string enum as well.

    enum Gender {
      MALE = 'male',
      FEMALE = 'female',
    }
    
    @prop({ enum: Gender })
    gender?: Gender;
    
  • lowercase: for strings only; whether to always call .toLowerCase() on the value.

    @prop({ lowercase: true })
    nickName?: string;
    
  • uppercase: for strings only; whether to always call .toUpperCase() on the value.

    @prop({ uppercase: true })
    nickName?: string;
    
  • trim: for strings only; whether to always call .trim() on the value.

    @prop({ trim: true })
    nickName?: string;
    
  • default: The provided value will be the default for that Mongoose property.

    @prop({ default: 'Nick' })
    nickName?: string;
    
  • _id: When false, no _id is added to the subdocument

    class Car {}
    
    @prop({ _id: false })
    car?: Car;
    
  • ref: By adding the ref option with another Typegoose class as value, a Mongoose reference property will be created. The type of the property on the Typegoose extending class should be Ref<T> (see Types section).

    class Car {}
    
    @prop({ ref: Car })
    car?: Ref<Car>;
    
  • refPath: Is the same as ref, only that it looks at the path specified, and this path decides which model to use

    class Car {}
    class Shop {}
    
    // in another class
    class Another {
      @prop({ required: true, enum: 'Car' | 'Shop' })
      which!: string;
    
      @prop({ refPath: 'which' })
      kind?: Ref<Car | Shop>;
    }
    
  • min / max (numeric validators): Same as Mongoose numberic validators.

    @prop({ min: 10, max: 21 })
    age?: number;
    
  • minlength / maxlength / match (string validators): Same as Mongoose string validators.

    @prop({ minlength: 5, maxlength: 10, match: /[0-9a-f]*/ })
    favouriteHexNumber?: string;
    
  • validate (custom validators): You can define your own validator function/regex using this. The function has to return a boolean or a Promise (async validation).

    // you have to get your own `isEmail` function, this is a placeholder
    
    @prop({ validate: (value) => isEmail(value)})
    email?: string;
    
    // or
    
    @prop({ validate: async (value) => { await isEmail(value) })
    email?: string;
    
    // or
    
    @prop({ validate: {
        validator: val => isEmail(val),
        message: `{VALUE} is not a valid email`
    }})
    email?: string;
    
    // or
    
    @prop({ validate: /\[email protected]\S+\.\S+/ })
    email?: string;
    
    // you can also use multiple validators in an array.
    
    @prop({ validate:
      [
        {
            validator: val => isEmail(val),
            message: `{VALUE} is not a valid email`
        },
        {
            validator: val => isBlacklisted(val),
            message: `{VALUE} is blacklisted`
        }
      ]
    })
    email?: string;
    
  • alias (alias): Same as Mongoose Alias, only difference is the extra property for type completion

    class Dummy {
      @prop({ alias: "helloWorld" })
      public hello: string; // will be included in the DB
      public helloWorld: string; // will NOT be included in the DB, just for type completion (gets passed as hello in the DB)
    }
    

Virtuals

  • Mongoose gives developers the option to create virtual properties. This means that actual database read/write will not occur these are just 'calculated properties'. A virtual property can have a setter and a getter. TypeScript also has a similar feature which Typegoose uses for virtual property definitions (using the prop decorator).

    example:

    class Name {
      @prop()
      firstName?: string;
    
      @prop()
      lastName?: string;
    
      // this will create a virtual property called 'fullName'
      get fullName() {
        return `${this.firstName} ${this.lastName}`;
      }
      set fullName(full) {
        const [firstName, lastName] = full.split(' ');
        this.firstName = firstName;
        this.lastName = lastName;
      }
    }
    

    DB Document:

    {
      _id: ObjectId("<some long id>"),
      firstName: "Will",
      lastName: "Smith"
    }
    
  • Non-Virtuals are supported too:
    example:

    function setFullName(val: string[]): string {
      return val.join(' ');
    }
    
    function getFullname(val: string): string[] {
      return val.split(' ');
    }
    
    class Name {
      @prop({ set: setFullName, get: getFullname })
      fullname: string[]; // this is just for type completion & getting the type for the schema
    }
    
    ...
    await NameModel.create({ fullname: ['Will', 'Smith'] });
    const [first, last]: string[] = (await NameModel.findOne({}).exec()).fullname;
    

    DB Document:

    {
      _id: ObjectId("<some long id>"),
      fullname: "Will Smith"
    }
    
  • Virtual-Populate is also supported by doing

    class RefClass {
      @prop({ required: true, ref: Virtual })
      public refToName: Ref<Virtual>;
    }
    class Name {
      @prop({ ref: RefClass, foreignField: 'refToName', localField: '_id', justOne: false })
      public somevalue: Ref<RefClass>;
    }
    

Options (look here for more details):

  • ref: This is like a normal ref [Required]
  • foreignField: Which property(on the ref-Class) to match localField against [Required]
  • localField: Which property(on the current-Class) to match foreignField against [Required]
  • justOne: Return as One Document(true) or as Array(false) [Optional]
  • count: Return the number of Documents found instead of the actual Documents [Optional]

arrayProp(options)

The arrayProp is a prop decorator which makes it possible to create array schema properties.

The options object accepts required, enum and default, just like the prop decorator. In addition to these the following properties exactly one should be given:

  • items: This will tell Typegoose that this is an array which consists of primitives (if String, Number, or other primitive type is given) or this is an array which consists of subdocuments (if it's extending the Typegoose class).

    @arrayProp({ items: String })
    languages?: string[];
    

Note that unfortunately the reflect-metadata API does not let us determine the type of the array, it only returns Array when the type of the property is queried. This is why redundancy is required here.

  • itemsRef: In mutual exclusion with items, this tells Typegoose that instead of a subdocument array, this is an array with references in it. On the Mongoose side this means that an array of Object IDs will be stored under this property. Just like with ref in the prop decorator, the type of this property should be Ref<T>[].

    class Car {}
    
    // in another class
    @arrayProp({ itemsRef: Car })
    previousCars?: Ref<Car>[];
    
  • itemsRefPath(IRP): Is the same as itemsRef only that it looks at the specified path of the class which specifies which model to use

    class Car {}
    class Shop {}
    
    // in another class
    class Another {
      @prop({ required: true, enum: ['Car', 'Shop'] })
      which!: string;
    
      @arrayProp({ itemsRefPath: 'which' })
      items?: Ref<Car | Shop>[];
    }
    

mapProp(options)

The mapProp is a prop decorator which makes it possible to create map schema properties.

The options object accepts enum and default, just like prop decorator. In addition to these the following properties are accepted:

  • of : This will tell Typegoose that the Map value consists of primitives (if String, Number, or other primitive type is given) or this is an array which consists of subdocuments (if it's extending the Typegoose class).

    class Car {
      @mapProp({ of: Car })
      public keys?: Map<string, Car>;
    }
    

Method decorators

Method Decorators are deprecated see Migrate to 6.0.0

Class decorators

Mongoose allows the developer to add pre and post hooks / middlewares to the schema. With this it is possible to add document transformations and observations before or after validation, save and more.

Typegoose provides this functionality through TypeScript's class decorators.

modelOptions

The Model Options can be used like below

@modelOptions({ existingMongoose, schemaOptions, existingConnection })
class Name {}

The Options for @modelOptions:

  • existingMongoose: mongoose: An existing Mongoose instance can also be passed down. If given, Typegoose uses this Mongoose instance's model methods.
  • schemaOptions: mongoose.SchemaOptions: Additional schema options can be passed down to the schema-to-be-created.
  • existingConnection: mongoose.Connection: An existing Mongoose connection can also be passed down. If given, Typegoose uses this Mongoose instance's model methods.

pre

We can simply attach a @pre decorator to the Typegoose class and define the hook function like you normally would in Mongoose. (Method supports REGEXP)

@pre<Car>('save', function(next) {
  if (this.model === 'Tesla') {
    this.isFast = true;
  }
  next();
})
class Car {
  @prop({ required: true })
  model!: string;

  @prop()
  isFast?: boolean;
}

This will execute the pre-save hook each time a Car document is saved. Inside the pre-hook Mongoose binds the actual document to this.

Note that additional typing information is required either by passing the class itself as a type parameter <Car> or explicity telling TypeScript that this is a Car (this: Car). This will grant typing informations inside the hook function.

post

Same as pre, the post hook is also implemented as a class decorator. Usage is equivalent with the one Mongoose provides. (Method supports REGEXP)

@post<Car>('save', (car) => {
  if (car.topSpeedInKmH > 300) {
    console.log(car.model, 'is fast!');
  }
})
class Car {
  @prop({ required: true })
  model!: string;

  @prop({ required: true })
  topSpeedInKmH!: number;
}

Of course this is not the document in a post hook (see Mongoose docs). Again typing information is required either by explicit parameter typing or by providing a template type.

plugin

Using the plugin decorator enables the developer to attach various Mongoose plugins to the schema. Just like the regular schema.plugin() call, the decorator accepts 1 or 2 parameters: the plugin itself, and an optional configuration object. Multiple plugin decorator can be used for a single Typegoose class.

If the plugin enhances the schema with additional properties or instance / static methods this typing information should be added manually to the Typegoose class as well.

import * as findOrCreate from 'mongoose-findorcreate';

@plugin(findOrCreate)
class User {
  // this isn't the complete method signature, just an example
  static findOrCreate(condition: DocumentType<User>):
    Promise<{ doc: DocumentType<User>, created: boolean }>;
}

const UserModel = getModelForClass(User);
const result = await UserModel.findOrCreate({ ... });

index

The @index decorator can be used to define advanced index types and index options not available via the index option of the @prop property decorator, such as compound indices, GeoJSON index types, partial indices, expiring documents, etc. Any values supported by MongoDB's createIndex() are also valid for @index. For more info refer to interface IndexOptions

@index({ article: 1, user: 1 }, { unique: true })
@index({ location: '2dsphere' })
@index({ article: 1 }, { partialFilterExpression: { stars: { $gte: 4.5 } } })
class Location {
 @prop()
 article?: number;

 @prop()
 user?: number;

 @prop()
 stars?: number;

 @arrayProp({ items: Array })
 location?: [[Number]]
}

Types

Some additional types were added to make Typegoose more user friendly.
(for some additional types, that are not exported by default can be accessed via import * as types from 'typegoose/types')

DocumentType

This is basically the logical 'and' of the T and the mongoose.Document, so that both the Mongoose instance properties/functions and the user defined properties/instance methods are available on the instance.

ReturnModelType

This is the logical 'and' of mongoose.Model<DocumentType<T>> and T, so that the Mongoose model creates DocumentType<T> typed instances and all user defined static methods are available on the model.

Ref

For reference properties: Ref<T> - T if populated and ObjectID if unpopulated. -> there are TypeGuards for this to check named:

  • isDocument(T): returns true if T is populated, false otherwise
  • isDocumentArray(T): returns true if T is an Array AND is fully populated

Improvements
  • Add Tests for:
    • Hooks: add hook test for pre & post with error
Notes
  • mongoose is a peer-dependency, and a dev dependency to install it for dev purposes
  • Please dont add comments with +1 or something like that, use the Reactions
  • Typegoose cannot be used with classes of the same name, it will always return the first build class with that name
  • All Models in Typegoose are set to strict by default, and cant be changed!
  • npm run doc generates all documentation for all files that can be used as modules (is used for github-pages)
  • npm run doc:all generates documentation even for internal modules
  • This module should not be used with ts-node --transpile-only, --type-check must be added at least, szokodiakos#196