Building a Full-Stack Web Application with Angular 15 and Spring Boot 3 from SCRATCH (2023)

Building a Full-Stack Web Application with Angular 15 and Spring Boot 3 from SCRATCH (2023)

This tutorial covers:

  1. Introduction
  2. Global Architecture of the project
  3. Creating a sample Angular application
  4. Create a Spring Boot application
  5. Building Backend( SpringBoot Project)
  6. Building Frontend ( Angular Project)
  7. Conclusion


Introduction

In today's interconnected world, building robust and scalable web applications is essential for businesses to thrive. In this article, we'll explore the process of creating a full-stack web application using Angular and Spring Boot. We'll cover the steps involved, from setting up the development environment to implementing the frontend interface and backend API. So, let's dive in!


In this tutorial, we are going to share how to build a Full-Stack Web Application with Angular 15 and Spring Boot 3 using MongoDB? NoSQL database.


This tutorial helps you cover the following topics:

  • Setting up Angular 15 and Spring Boot 3 for full-stack development.
  • Integrating Angular with Spring Boot to create a seamless frontend and backend integration.
  • Implementing RESTful APIs in Spring Boot for data retrieval and manipulation.
  • Working with MongoDB to store and retrieve data efficiently.
  • Designing and developing dynamic and responsive user interfaces using Angular and Bootstrap.
  • Managing routing in Angular to create a smooth and intuitive user experience.
  • Consuming APIs in Angular to retrieve and display data, as well as perform various operations.

Global Architecture of the project

1- Micro services Architecture

Microservices refer to both an architecture and a software development approach that involves breaking down applications into smaller, independent components. Unlike traditional monolithic approaches, where all components are tightly coupled as a single entity, microservices focus on creating loosely coupled and independently deployable services.

No alt text provided for this image
Architecture Micro-services vs Architecture Monolitique

2- Logical architecture

Our application follows a logical architecture composed of three layers:

  1. Presentation Layer: This layer is responsible for user interaction with the application. It includes the user interface components, such as web pages or mobile screens, that allow users to input data, view information, and interact with the application's features and functionalities.
  2. Data Access Layer: This layer handles communication with the data source. It includes components and services responsible for retrieving and storing data from and to the database or any other data storage mechanism. This layer encapsulates the logic for accessing and manipulating data, providing an abstraction to the upper layers.
  3. Business Logic Layer: This layer acts as a bridge between the presentation layer and the data access layer. It contains the core business logic and rules that govern the application's behavior. It processes and validates user inputs, performs complex calculations or transformations, and coordinates the interaction between the presentation layer and the data access layer.

By dividing the application into these three layers, we achieve separation of concerns and maintain a clear separation between user interface, data manipulation, and business rules. This architectural pattern enhances modularity, scalability, and maintainability of the application, as each layer can be developed, tested, and modified independently, facilitating code reuse and improving overall system performance and flexibility.

No alt text provided for this image
Logical architecture

Creating a sample Angular application

Step 1 : Installing the Angular CLI

The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications directly from a command shell.

In order to create an Angular project, it is necessary to install the Angular CLI on the development system. This requires the installation of Node.js.

Follow the Node.js installation URL on your developer environment.

Install the CLI using the npm package manager on a terminal:

npm install -g @angular/cli        

Step 2 : Creating an Angular Project

To get started, let's create a fresh Angular project using Angular CLI by typing the command below (on a terminal) with the project name “angular-springboot-app”.

ng new angular-springboot-app        

Hit y to a dd Angular Routing to your project

Hit Enter to choose stylesheet format css (Cascading Style Sheets)

Once the Angular project is setup then get into the project folder by using the following command.

cd angular-springboot-app        

Step 3 : Open Angular Project

Your basic Angular app is almost ready just hit the below command in terminal.

ng serve --open        

Now your browser should be opened and you'll see this

No alt text provided for this image
*

Now everything should be fine. Let's jump to the back-end.

Create a Spring Boot application

Step 1 : Installing the JRE, SpringTools

To start developing Spring Boot project, It is important to ensure that you have Java installed on your system. If you haven't already installed Java, you can follow the Java installation URL

Once Java is successfully installed, the next step is to set up an Integrated Development Environment (IDE) to work with Spring Boot. One popular choice is the Spring Tools Suite (STS). You can download and install STS by visiting the Spring Tools website.

By installing Java and then configuring STS, you will have a robust development environment ready to create an amazing Spring Boot application. So, let's get to building our project.

Step 2 : Creating an Springboot Project

To get started, let's create a fresh Springboot project using spring initializr by following the provided information in the picture below

No alt text provided for this image

Customize your project as per your requirements, but it is recommended to keep the Java version in your Spring Boot project equal to or lower than the version installed on your computer.

Next add these Depandencies :

  • Spring Web : Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
  • Spring Data MongoDB : Store data in flexible, JSON-like documents, meaning fields can vary from document to document and data structure can be changed over time.

Once you have completed setting up your Spring Boot project, it's time to generate the project by clicking the "Generate" button, as shown in the picture below

No alt text provided for this image

Congratulations! You now have a fully functional Spring Boot project downloaded to your computer.

Step 3: Installing MongoDB (DataBase)

To integrate MongoDB as our NoSQL database in our Spring Boot application, we need to install MongoDB Compass on our system. Follow these steps to install MongoDB:

  1. Visit the official MongoDB website and navigate to the "Downloads" section.
  2. Choose the MongoDB Compass (GUI) version of MongoDB for your operating system and download the installer.
  3. Once the installer is downloaded, run the installation file and follow the installation wizard prompts. Make sure to select the desired installation options and specify the installation directory.

Once you have completed the installation of MongoDB, you can proceed to launch MongoDB Compass, a graphical user interface (GUI) tool, to manage and interact with your MongoDB server. MongoDB Compass provides a user-friendly interface for performing various database operations and querying data.

  1. Locate the MongoDB Compass application on your computer and launch it.
  2. After opening MongoDB Compass, you will be prompted to create a new connection. Click on the "New Connection" button.
  3. In the connection settings, ensure that the "Hostname" field is set to "localhost" and the "Port" field is set to the default MongoDB port, which is usually 27017.
  4. Click on the "Connect" button to establish a connection to your MongoDB server.
  5. If the connection is successful, MongoDB Compass will display a visual representation of your server, including databases and collections, similar to the picture below:

No alt text provided for this image


By using MongoDB Compass, you can perform a variety of tasks, such as creating databases, managing collections, inserting and updating documents, executing queries, and more. The intuitive GUI provided by MongoDB Compass simplifies the process of interacting with your MongoDB server.

Now that you have MongoDB Compass up and running, you are ready to configure your Spring Boot application to connect to MongoDB and leverage its powerful features for data storage and retrieval.


If you find these concepts overwhelming or confusing, don't worry! Now it's time to dive into the exciting part: coding.


Building Backend(SpringBoot Project)

Step 1: upload project in IDE

To start building the backend of your web application using Spring Boot, you'll need to upload the project to your Integrated Development Environment (IDE). Follow these steps to import the project into your IDE:

  1. Launch your preferred IDE (such as IntelliJ IDEA, Eclipse, or Spring Tools Suite<STS>).
  2. In your IDE, select the option to import an existing project or open a project from the file menu.
  3. Navigate to the location where you downloaded and extracted the Spring Boot project.t
  4. Select the root directory of the project and click "finish" or "Import" to begin the import process.

No alt text provided for this image
Porject files Tree


Step 2: Creating Packages

After uploading your Spring Boot project to your IDE, the next step is to organize your project structure by creating packages. Packages provide a logical grouping of related classes and components within your project. Follow these steps to create packages in your Spring Boot project

  1. Right-click on the source directory of your project (usually named "src" or "src/main/java") in your IDE's project explorer.
  2. Select the "New" or "Create New" option, then choose "Package" to create a new package.

We will create 4 Packages divied like this :

  1. Controllers : This package will contain all the classes responsible for handling the incoming HTTP requests and defining the API endpoints for our application.
  2. Repositories : This package will contain all the classes responsible for database interactions, such as querying and persisting data.
  3. Services : This package will contain all the classes that implement the business logic of our application. Services act as an intermediate layer between controllers and repositories, encapsulating the application's core functionalities.
  4. Entities : This package will house the entity classes that represent the data models of our application. Entities define the structure and relationships between the data objects in our system.

No alt text provided for this image
No alt text provided for this image

Step 3: Creating Entities

Entities represent the data models of your application and define the structure and relationships between the data objects. In this step, we will create the entity classes within the "entities" package.

For the purpose of this tutorial, we will demonstrate a simple example using a single case of a relationship between classes. If you're interested in exploring more complex relationships, such as one-to-many, many-to-one, or many-to-many, you can refer to the provided link for detailed information and examples.

Link: A Guide to @DBRef in MongoDB

Follow these steps to create the entities in your Spring Boot application:

  1. Open your IDE and navigate to the "entities" package that you previously created.
  2. Create a new class. For example, let's create two class a "User" entity class and a "Role" entity class.

Role Entity

package com.linkedin.demo.springboot.entities;

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

@Document(collection = "Roles")
public class Role implements Serializable{
	@Id
	private String id;
	private String roleName;
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getRoleName() {
		return roleName;
	}
	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
}        

User Entity

package com.linkedin.demo.springboot.entities;

import java.io.Serializable;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "Users")
public class User implements Serializable {
	@Id
	private String id;
	private String userName;
	private String lastName;
	@DBRef
? ? private Role role;
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public Role getRole() {
		return role;
	}
	public void setRole(Role role) {
		this.role = role;
	}
}        

  • In MongoDB, the @Document annotation is used to mark a class as a document that will be stored in the database. It is part of the Spring Data MongoDB framework, which provides convenient abstractions for working with MongoDB.
  • The @Id annotation is used in Spring Data MongoDB to mark a field as the identifier or primary key of a document in the MongoDB database. It is typically applied to a field within a class that is annotated with @Document.
  • The @DBRef annotation is used in Spring Data MongoDB to establish a reference to another document in the MongoDB database. It allows you to define relationships between documents and enables the retrieval of related documents when querying.

Step 4: Creating Repositories

Repositories in Spring Boot provide a convenient way to interact with the database and perform CRUD (Create, Read, Update, Delete) operations on your entities. In this step, we will create repository interfaces within the "repositories" package of our Spring Boot project.

Role Repository

package com.linkedin.demo.springboot.repositories;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.linkedin.demo.springboot.entities.Role;

@Repository
public interface RoleRepository extends MongoRepository<Role,String>{}        

User Repository

package com.linkedin.demo.springboot.repositories;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.linkedin.demo.springboot.entities.User;

@Repository
public interface UserRepository extends MongoRepository<User,String>{}        

The @Repository annotation is used in Spring Framework to indicate that a class is a repository or a data access object (DAO). It is typically applied to classes that perform database operations, such as querying, inserting, updating, or deleting data.

Step 5: Creating Services(CRUD)

Services in a Spring Boot application handle the business logic and act as an intermediary between the controllers and repositories. In this step, we will create service classes within the "services" package of your Spring Boot project to perform CRUD (Create, Read, Update, Delete) operations on the entities.

Role Service

package com.linkedin.demo.springboot.services;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.linkedin.demo.springboot.entities.Role;
import com.linkedin.demo.springboot.repositories.RoleRepository;

@Service
public class RoleService {

	@Autowired
	RoleRepository roleRepository;

	// function to create a new role
	public Role createRole(Role role) {
? ? ? ? return roleRepository.save(role);
? ? }
	// function return a role by id or null in case it doesn't exist
? ? public Role getRoleById(String id) {
? ? ? ? return roleRepository.findById(id).orElse(null);
? ? }
? ? // function that retrieve all roles from database
? ? public List<Role> getAllRoles() {
? ? ? ? return roleRepository.findAll();
? ? }
? ? // function that update an existing role in database
? ? public Role updateRole(Role role) {
? ? ? ? return roleRepository.save(role);
? ? }
? ? // function that remove a role from database by it's id
? ? public void deleteRole(String id) {
? ? 	roleRepository.deleteById(id);
? ? }
}        

User Service

package com.linkedin.demo.springboot.services;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.linkedin.demo.springboot.entities.User;
import com.linkedin.demo.springboot.repositories.UserRepository;

@Service
public class UserService {
	@Autowired
	UserRepository userRepository;
	// function to create a new user
	public User createUser(User user) {
? ? ? ? return userRepository.save(user);
? ? }
	// function return a user by id or null in case it doesn't exist
? ? public User getUserById(String id) {
? ? ? ? return userRepository.findById(id).orElse(null);
? ? }
? ? // function that retrieve all users from database
? ? public List<User> getAllUsers() {
? ? ? ? return userRepository.findAll();
? ? }
? ? // function that update an existing user in database
? ? public User updateUser(User user) {
? ? ? ? return userRepository.save(user);
? ? }
? ? // function that remove a user from database by it's id
? ? public void deleteUser(String id) {
? ? ? ? userRepository.deleteById(id);
? ? }
}        

  • The @Service annotation is used in Spring Framework to indicate that a class is a service component. It is typically applied to classes that contain business logic or perform operations related to the business domain.
  • The @Autowired annotation is used in Spring Framework to automatically wire dependencies between components. It is typically applied to fields, constructors, or setter methods within a class.

Step 6: Creating Controllers

In this step, we will create controllers in our Spring Boot project. Controllers are responsible for handling incoming HTTP requests, processing them, and returning appropriate responses.

User Controller

package com.linkedin.demo.springboot.controllers;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.linkedin.demo.springboot.entities.User;
import com.linkedin.demo.springboot.services.UserService;

@CrossOrigin(origins="*")
@RestController
@RequestMapping("/user")
public class UserController {
	@Autowired
	UserService userService;

	//Create new User
	@PostMapping(consumes =MediaType.APPLICATION_JSON_VALUE,produces =MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<User> createUser(@RequestBody User user){
		User newUser = userService.createUser(user);
		return new ResponseEntity<User>(newUser,HttpStatus.CREATED);
	}
	//Get user by id
? ? @GetMapping(path="/{userId}",produces =MediaType.APPLICATION_JSON_VALUE)
? ? public ResponseEntity<User> getUserById(@PathVariable String userId) {
? ? 	User userFound = userService.getUserById(userId);
? ? 	return new ResponseEntity<User>(userFound,HttpStatus.OK);	
? ? }
? ? //Get all users
? ? @GetMapping(produces =MediaType.APPLICATION_JSON_VALUE)
? ? public ResponseEntity<List<User>> getAllUsers() {
? ? 	List<User> usersList = userService.getAllUsers();
? ? 	return new ResponseEntity<List<User>>(usersList,HttpStatus.OK);	
? ? }
? ? //Update user
? ? @PutMapping(consumes =MediaType.APPLICATION_JSON_VALUE,produces =MediaType.APPLICATION_JSON_VALUE)
? ? public ResponseEntity<User> updateUser(@RequestBody User user){
? ? 	User UserUpdated = userService.updateUser(user);
? ? 	return new ResponseEntity<User>(UserUpdated,HttpStatus.ACCEPTED);	
? ? }
? ? //Delete user
	@DeleteMapping(path="/{id}")
	public ResponseEntity<Object> deleteUser(@PathVariable String id) {
		userService.deleteUser(id);
		return new ResponseEntity<>(HttpStatus.NO_CONTENT);
	}
}        

Role Controller

package com.linkedin.demo.springboot.controllers;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.linkedin.demo.springboot.entities.Role;
import com.linkedin.demo.springboot.services.RoleService;

@CrossOrigin(origins="*")
@RestController
@RequestMapping("/role")
public class RoleController {
	@Autowired
	RoleService roleService;
	//Create new Role
	@PostMapping(consumes =MediaType.APPLICATION_JSON_VALUE,produces =MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<Role> createRole(@RequestBody Role role){
		Role newRole = roleService.createRole(role);
		return new ResponseEntity<Role>(newRole,HttpStatus.CREATED);
	}
	//Get role by id
? ? @GetMapping(path="/{roleId}",produces =MediaType.APPLICATION_JSON_VALUE)
? ? public ResponseEntity<Role> getRoleById(@PathVariable String roleId) {
? ? 	Role roleFound = roleService.getRoleById(roleId);
? ? 	return new ResponseEntity<Role>(roleFound,HttpStatus.OK);	
? ? }
? ? //Get all roles
? ? @GetMapping(produces =MediaType.APPLICATION_JSON_VALUE)
? ? public ResponseEntity<List<Role>> getAllRoles() {
? ? 	List<Role> rolesList = roleService.getAllRoles();
? ? 	return new ResponseEntity<List<Role>>(rolesList,HttpStatus.OK);	
? ? }
? ? //Update role
? ? @PutMapping(consumes =MediaType.APPLICATION_JSON_VALUE,produces =MediaType.APPLICATION_JSON_VALUE)
? ? public ResponseEntity<Role> updateUser(@RequestBody Role role){
? ? 	Role roleUpdated = roleService.updateRole(role);
? ? 	return new ResponseEntity<Role>(roleUpdated,HttpStatus.ACCEPTED);	
? ? }
? ? //Delete role
	@DeleteMapping(path="/{id}")
	public ResponseEntity<Object> deleteRole(@PathVariable String id) {
		roleService.deleteRole(id);
		return new ResponseEntity<>(HttpStatus.NO_CONTENT);
	}
}        

Here's a breakdown of all annotations:

@GetMapping:

  • Used to handle HTTP GET requests.
  • Maps a specific URL or URL pattern to a method in the controller class.
  • The method annotated with @GetMapping will be invoked when the corresponding URL is accessed via a GET request.
  • It is commonly used to retrieve data or resources from the server.

@PutMapping:

  • Used to handle HTTP PUT requests.
  • Maps a specific URL or URL pattern to a method in the controller class.
  • The method annotated with @PutMapping will be invoked when the corresponding URL is accessed via a PUT request.
  • It is commonly used to update or modify existing data or resources on the server.

@DeleteMapping:

  • Used to handle HTTP DELETE requests.
  • Maps a specific URL or URL pattern to a method in the controller class.
  • The method annotated with @DeleteMapping will be invoked when the corresponding URL is accessed via a DELETE request.
  • It is commonly used to delete or remove data or resources from the server.

@PostMapping:

  • Used to handle HTTP POST requests.
  • Maps a specific URL or URL pattern to a method in the controller class.
  • The method annotated with @PostMapping will be invoked when the corresponding URL is accessed via a POST request.
  • It is commonly used to create or add new data or resources on the server.

`@CrossOrigin(origins="*")`:

  • Used to enable Cross-Origin Resource Sharing (CORS) in Spring Boot applications.
  • Allows web browsers to make requests to your application from different origins or domains.
  • The `origins` parameter specifies the list of allowed origins. Using `"*"` allows requests from any origin.
  • It is commonly used when you need to make AJAX requests or fetch data from a different domain.

`@RestController`:

  • A specialized version of the `@Controller` annotation.
  • Used to mark a class as a RESTful controller.
  • Combines the functionality of `@Controller` and `@ResponseBody`.
  • Automatically serializes the return value of controller methods to the HTTP response body.
  • Typically used when building RESTful APIs.

`@RequestMapping("/role")`:

  • Used to map URLs or URL patterns to methods in a controller class.
  • Specifies the base URL or URL pattern for the controller's endpoints.
  • In this case, the controller handles requests related to the "role" path.

`consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE`:

  • Used to specify the media types that a RESTful API can consume and produce.
  • `consumes` specifies the media types that the API can consume (accept as request body).
  • `produces` specifies the media types that the API can produce (return as response body).
  • In this case, the API can consume and produce JSON data.

`@PathVariable`:

  • Used to bind a URL path variable to a method parameter.
  • Allows you to extract dynamic values from the URL path.
  • The value of the path variable is passed to the method parameter for further processing.
  • It is commonly used to handle URL patterns with dynamic segments.

`@RequestBody`:

  • Used to bind the request body to a method parameter.
  • Binds the content of the HTTP request body to the annotated method parameter.
  • The request body is automatically deserialized to the specified parameter type.
  • It is commonly used to extract data from the request body in POST or PUT requests.


These annotations and parameters provide additional functionality and configuration options for your Spring Boot controllers and RESTful APIs. They allow you to handle different types of requests, specify media types, bind URL variables and request bodies, and control cross-origin resource sharing.

Step 7: Connect Data base to our project

In this step, we will connect our Spring Boot project to the MongoDB database that we previously installed. Connecting the database to our project will allow us to perform data operations and interact with the database using our application.

To connect the database to our project, follow these steps:

  1. Open the application.properties file located in the src/main/resources directory of your Spring Boot project.
  2. Add the following configuration properties to the application.properties file:

spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=your-database-name
server.servlet.context-path=/api?
server.port=8081        

Replace your-database-name with the name of the MongoDB database you want to connect to.

You can also change the server port for exemple : 8082,8083....

Or you can also change the context path such as '/myapi' .....

Then save the application.properties file.

By adding these configuration properties, we specify the host, port, and database name for our MongoDB connection.

Step 8: Run the project

In this step, we will run our Spring Boot project to start the server and make it accessible for testing and interaction with the front-end.

Before running the Spring Boot application, ensure that your MongoDB database is up and running. Start MongoDB Compass or any other MongoDB client to ensure the database server is operational.

Once you have confirmed that the database server is running, follow these steps to run the Spring Boot application:

  1. Locate the green top button in your IDE and click on the black arrow next to it. This will open the run configurations menu.
  2. In the run configurations menu, select your project that has the main class. This class is typically annotated with @SpringBootApplication and contains the main method.
  3. After selecting your project, click on the "Run" button or choose the option to run the configuration. This will compile your project and start the Spring Boot server.
  4. Monitor the console output in your IDE to see the log messages from the Spring Boot application. You should see messages indicating the successful startup of the server.

Once the application is running, you can test the API endpoints using tools like Postman or by accessing the URLs directly in your browser. Make requests to the defined endpoints to interact with the application and verify its functionality.

Remember to keep the application running as long as you want to test or use it. If you want to stop the application, you can click on the stop button in your IDE or terminate the process.

No alt text provided for this image

Step 9: Testing the Application Using Postman

Once your Spring Boot application is running, you can test the API endpoints using Postman. Postman is a popular tool for API development and testing, allowing you to send HTTP requests and view the responses.

To test your application using Postman, follow these steps:

  1. Open Postman or download it from the official website if you haven't already installed it.
  2. Create a new request by clicking on the "New" button in the Postman interface.
  3. Set the HTTP method (GET, POST, PUT, DELETE) based on the API endpoint you want to test.
  4. Enter the URL of the API endpoint you want to test. For example, We have a role-related API endpoint at https://localhost:8081/api/role, enter this URL in Postman.
  5. Click on the "Send" button to send the request to your Spring Boot application.
  6. View the response in the Postman interface. You can see the response status code, headers, and body.
  7. Analyze the response to ensure that it matches your expectations and that the API is functioning correctly.
  8. Repeat the process for other API endpoints or different types of requests as needed.

No alt text provided for this image
Get all Roles
No alt text provided for this image
Create new role
No alt text provided for this image
update existing user
No alt text provided for this image
Delete Role

Conclusion

In conclusion, we have successfully set up the role and user functionalities in our Spring Boot application. We have created the necessary packages, entities, repositories, services, and controllers to handle the CRUD operations related to roles and users. With these components in place, we can now focus on building the front-end of our application using Angular

Building Frontend (Angular Project)

Now that we have completed the backend part using Spring Boot, it's time to shift our focus to the frontend development. We will start by setting up the necessary imports and dependencies.

Step 1: Importing Modules and Dependencies

To begin, let's make sure we have the required modules and dependencies imported into our Angular project. Open the index.html file located in the src folder. Inside the <head> section, add the following import statements to import Bootstrap

<!doctype html>
<html lang="en">
<head>
? <meta charset="utf-8">
? <title>AngularSpringbootApp</title>
? <base href="/">
? <meta name="viewport" content="width=device-width, initial-scale=1">
? <link rel="icon" type="image/x-icon" href="favicon.ico">

? <!--Bootstrap import CSS-->
? <link rel="stylesheet"  integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
? <!--End Bootstrap imports-->


</head>
<body>
? <app-root></app-root>

? <!--Bootstrap import JS-->
? <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
? <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
? <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
? <!--End Bootstrap imports-->
</body>
</html>
        

These imports will give our application access to the Bootstrap CSS framework for styling and the Font Awesome library for icons.

Next, let's move to our Angular modules. Open the app.module.ts file located in the src/app folder. Add the following import statements :

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

imports:[
? ? BrowserModule,
? ? AppRoutingModule,
? ? HttpClientModule,
    FormsModule
? ],
? providers: [],
? bootstrap: [AppComponent]        

Step 2: Manage Angular Models

In this step, we will create and manage Angular models, which will represent the data structures used in our application. Models define the shape and properties of our data objects, providing a structured and type-safe way to handle data.

To create Angular models type the command below (on a terminal)

ng generate class models/Role        

In the above command, models represents the folder where you want to generate the model, and Role is the name of the model. You can modify these values according to your project structure and desired model name.

After setting up the necessary imports and dependencies, we can now proceed to add the necessary variables to our class.

export class Role {
? id : string;
? roleName : string;

? constructor(){
? ? this.id = "";
? ? this.roleName = "";
? }
}        

Next the User Model ,to create User model type the command below (on a terminal)

ng generate class models/User        

We can now proceed to add the necessary variables to our class.

import { Role } from "./role";

export class User {
? id : string;
? userName : string;
? lastName : string;
? role : Role;

? constructor(){
? ? this.id = "";
? ? this.userName = "";
? ? this.lastName = "";
? ? this.role = new Role();
? }
}        

Step 3: Manage Angular Services

Now that we have created our models, it's time to manage our Angular services. Services in Angular provide a way to encapsulate and share data, logic, and functionality across different components.

To create a new service, run the following commands in your Angular project's root directory:

ng g service Services/user
ng g service Services/role
ng g service Services/data        

Inside the generated service file, you can define variables, methods, and logic specific to your service's functionality.

Let's start with editing our DataService.ts

import { Inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';


@Injectable({
? providedIn: 'root'
})
export class DataService {


? ?// Inject ApiUrl in constructor to Get it form ather Service
? ?constructor(@Inject(String) private APIUrl: string,private http: HttpClient) { }


? // Get Method
? getAll(): Observable<any> {
? ? return this.http.get<any>(this.APIUrl);
? }
? // Get with id
? get(id: any): Observable<any> {
? ? return this.http.get(`${this.APIUrl}/${id}`);
? }
? // Update Method
? Update(data: any): Observable<any> {
? ? return this.http.put(`${this.APIUrl}`, data);
? }
? // Create Method
? Create(data: any): Observable<any> {
? ? return this.http.post(this.APIUrl, data);
? }
? // Delete Method
? Delete(id: any): Observable<any> {
? ? return this.http.delete(`${this.APIUrl}/${id}`);
? }
}        

After creating our DataService, we can move on to other services in our application. These services will primarily depend on the DataService to interact with the API and perform various operations. The advantage of this approach is that we only need to inject the API URL into the DataService, which will provide all the necessary functions.

Let's take an example of a UserService that handles user-related operations. In the UserService, we can inject the DataService and configure it with the API URL:

import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { HttpClient } from '@angular/common/http';

const ?APIUrlUser ="https://localhost:8081/api/user";

@Injectable({
? providedIn: 'root'
})
export class UserService extends DataService{
? constructor(http:HttpClient){
? ? super(APIUrlUser,http);
? }
}        

And we do the same with RoleService

import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { HttpClient } from '@angular/common/http';

const ?APIUrlRole ="https://localhost:8081/api/role";

@Injectable({
? providedIn: 'root'
})
export class RoleService extends DataService{
? constructor(http:HttpClient){
? ? super(APIUrlRole,http);
? }
}        

Step 3: Setting up Angular Routing

Before customizing our application and creating new components, let's set up Angular routing to navigate between different views or pages.

Define the routes for your application by creating an array of route objects. Each route object should have a path and component property. For example:

import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router';
import { UserComponent } from './Components/user/user.component';
import { RoleComponent } from './Components/role/role.component';

const routes: Routes = [
? {path : "users" , component : UserComponent},
? {path : "roles" , component : RoleComponent},
? { path: '', ? redirectTo: '/users', pathMatch: 'full' }, // redirect to `users-component` if the path is empty
];


@NgModule({
? imports: [RouterModule.forRoot(routes)],
? exports: [RouterModule]
})
export class AppRoutingModule { };        

In this example, we define routes for the user and role pages. The empty path '' redirects to the /users route.

Step 4: Manage Angular Components

Now that we have set up our services and configured the necessary API interactions, it's time to customize our application by creating new components and utilizing Bootstrap for styling. Additionally, we will consume APIs using our services to display data and perform other operations.

To create a new component, run the following commands in your Angular project's root directory:

ng g component Components/role
ng g component Components/user        

We have created the UserComponent and RoleComponent as part of our application. Now, let's add a navigation bar to the app.component.html file to facilitate routing between different components using routerLink and routerLinkActive directives.

  1. Open the app.component.html file located in the src/app folder.
  2. Replace the existing content with the following code to add a basic navigation bar:

<nav class="navbar navbar-dark bg-dark">
? <a class="navbar-brand" href="#">Angular springBoot Application</a>
? <div class="navbar-collapse" id="navbarNav">
? ? <ul class="navbar-nav">
? ? ? <li class="nav-item" routerLinkActive="active">
? ? ? ? <a class="nav-link" routerLink="/users">Users</a>
? ? ? </li>
? ? ? <li class="nav-item" routerLinkActive="active">
? ? ? ? <a class="nav-link" routerLink="/roles">Roles</a>
? ? ? </li>
? ? </ul>
? </div></nav>
<router-outlet></router-outlet>
        

Next, let's move on to designing the UserComponent and RoleComponent. Open the respective component files (user.component.ts, user.component.html, role.component.ts, role.component.html) located in the src/app/Components folder.

In the component HTML files, you can design the user and role components as per your application requirements. Utilize Bootstrap classes and HTML elements to structure and style the components.

In role.component.html :

<button type="button" class="btn btn-success" data-toggle="modal" data-target="#EditAddRole" (click)="openModel()" style="margin: 50px;">Create New Role</button>
<table class="table">
? <thead>
? ? <tr>
? ? ? <th scope="col">#</th>
? ? ? <th scope="col">Name Role</th>
? ? ? <th scope="col" colspan="2">Action</th>
? ? </tr>
? </thead>
? <tbody>
? ? <tr *ngFor="let role of roleList" >
? ? ? <th scope="row">{{role.id}}</th>
? ? ? <td>{{role.roleName}}</td>
? ? ? <td>
? ? ? ? <button type="button" class="btn btn-primary" ?data-toggle="modal" data-target="#EditAddRole" (click)="openModel(role)">Edit</button>
? ? ? </td>
? ? ? <td>
? ? ? ? <button type="button" class="btn btn-danger" (click)="deleteRole(role.id)">Delete</button>
? ? ? </td>
? ? </tr>
? </tbody>
</table>

<!-- Modal -->
<div class="modal fade" id="EditAddRole" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
? <div class="modal-dialog modal-dialog-centered" role="document">
? ? <div class="modal-content">
? ? ? <div class="modal-header">
? ? ? ? <h5 class="modal-title" id="exampleModalLongTitle">Role</h5>
? ? ? ? <button type="button" class="close" data-dismiss="modal" aria-label="Close">
? ? ? ? ? <span aria-hidden="true">&times;</span>
? ? ? ? </button>
? ? ? </div>
? ? ? <div class="modal-body">
? ? ? ? <div class="input-group mb-3">
? ? ? ? ? <div class="input-group-prepend">
? ? ? ? ? ? <span class="input-group-text" id="inputGroup-sizing-default">Role Name</span>
? ? ? ? ? </div>
? ? ? ? ? <input type="text" class="form-control" [(ngModel)]="newRole.roleName">
? ? ? ? </div>
? ? ? </div>
? ? ? <div class="modal-footer">
? ? ? ? <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
? ? ? ? <button type="button" class="btn btn-primary" *ngIf="creatingMode" (click)="createRole()">Create User</button>
? ? ? ? <button type="button" class="btn btn-primary" *ngIf="!creatingMode" (click)="modifyRole()">Modify User</button> ? ? ?</div>
? ? </div>
? </div>
</div>        

Now, let's consume APIs from services to display data or perform other operations. Inject the necessary service into the component's constructor, and utilize the service methods to fetch and manipulate data.

In role.component.ts

import { Component } from '@angular/core';
import { RoleService } from 'src/app/Services/role.service';
import { Role } from 'src/app/models/role';


@Component({
? selector: 'app-role',
? templateUrl: './role.component.html',
? styleUrls: ['./role.component.css']
})
export class RoleComponent {
? roleList : Role[] = [];
? // this variable is get data from model
? newRole : Role = new Role();
? // this variable determines wither we are in changing or creating new user
? creatingMode : boolean = true;
? constructor(private roleService : RoleService){
? ? this.getAllRoles();
? }
  //Get All roles from database
? getAllRoles(){
? ? this.roleService.getAll().subscribe((response : Role[])=>{
? ? ? this.roleList = response;
? ? });
? }
  //Delete role by id
? deleteRole(roleId : string){
? ? if(confirm("Are you sure you want to delete this role !!!")){
? ? ? this.roleService.Delete(roleId).subscribe(()=>{
? ? ? ? alert("Role Deleted Successfully");
? ? ? ? window.location.reload();
? ? ? });
? ? }
? }
  //Create new role in database
? createRole(){
? ? const newRole = {
? ? ? roleName : this.newRole.roleName
? ? }
? ? this.roleService.Create(newRole).subscribe(()=>{
? ? ? alert("Role Created Successfully");
? ? ? window.location.reload();
? ? });
? }
  //Update role
? modifyRole(){
? ? this.roleService.Update(this.newRole).subscribe(()=>{
? ? ? alert("Role Updated Successfully");
? ? ? window.location.reload();
? ? })
? }
  // function to verify the event
? openModel(role: Role = new Role()){
? ? if(role.id == ""){
? ? ? this.newRole = new Role();
? ? }else{
? ? ? this.creatingMode = false;
? ? ? this.newRole = role;
? ? }
? }
}        

Now, let's navigate from the RoleComponent to the UserComponent in our Angular application.

Open the user.component.html file located in the src/app/Components folder.

<button type="button" class="btn btn-success" data-toggle="modal" data-target="#EditAddUser" (click)="openModel()" style="margin: 50px;">Create New User</button>
<table class="table">
? <thead>
? ? <tr>
? ? ? <th scope="col">#</th>
? ? ? <th scope="col">First</th>
? ? ? <th scope="col">Last</th>
? ? ? <th scope="col">Role Name</th>
? ? ? <th scope="col" colspan="2">Action</th>
? ? </tr>
? </thead>
? <tbody>
? ? <tr *ngFor="let user of userList" >
? ? ? <th scope="row">{{user.id}}</th>
? ? ? <td>{{user.userName}}</td>
? ? ? <td>{{user.lastName}}</td>
? ? ? <td>{{user.role.roleName}}</td>
? ? ? <td>
? ? ? ? <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#EditAddUser" (click)="openModel(user)">Edit</button>
? ? ? </td>
? ? ? <td>
? ? ? ? <button type="button" class="btn btn-danger" (click)="deleteUser(user.id)">Delete</button>
? ? ? </td>
? ? </tr>
? </tbody>
</table>
<!-- Modal -->
<div class="modal fade" id="EditAddUser" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
? <div class="modal-dialog modal-dialog-centered" role="document">
? ? <div class="modal-content">
? ? ? <div class="modal-header">
? ? ? ? <h5 class="modal-title" id="exampleModalLongTitle">Role</h5>
? ? ? ? <button type="button" class="close" data-dismiss="modal" aria-label="Close">
? ? ? ? ? <span aria-hidden="true">&times;</span>
? ? ? ? </button>
? ? ? </div>
? ? ? <div class="modal-body">
? ? ? ? <div class="input-group">
? ? ? ? ? <div class="input-group-prepend">
? ? ? ? ? ? <span class="input-group-text" id="">First and last name</span>
? ? ? ? ? </div>
? ? ? ? ? <input type="text" class="form-control" [(ngModel)]="userToModify.userName">
? ? ? ? ? <input type="text" class="form-control" [(ngModel)]="userToModify.lastName">
? ? ? ? </div><br>
? ? ? ? <div class="input-group mb-3">
? ? ? ? ? <div class="input-group-prepend">
? ? ? ? ? ? <label class="input-group-text" for="inputGroupSelect01">Roles</label>
? ? ? ? ? </div>
? ? ? ? ? <select class="custom-select" id="inputGroupSelect01" [(ngModel)]="userToModify.role.id">
? ? ? ? ? ? <option selected>Choose...</option>
? ? ? ? ? ? <option *ngFor="let role of roleList" ?[value]="role.id">{{role.roleName}}</option>
? ? ? ? ? </select>
? ? ? ? </div>
? ? ? </div>
? ? ? <div class="modal-footer">
? ? ? ? <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
? ? ? ? <button type="button" class="btn btn-primary" *ngIf="creatingMode" (click)="CreateUser()">Create User</button>
? ? ? ? <button type="button" class="btn btn-primary" *ngIf="!creatingMode" (click)="modifyUser()">Modify User</button>
? ? ? </div>
? ? </div>
? </div>
</div>        

Now, let's consume APIs from services to display data or perform other operations. Inject the necessary service into the component's constructor, and utilize the service methods to fetch and manipulate data.

Open the user.component.ts file located in the src/app/Components folder.

import { Component } from '@angular/core';
import { RoleService } from 'src/app/Services/role.service';
import { UserService } from 'src/app/Services/user.service';
import { Role } from 'src/app/models/role';
import { User } from 'src/app/models/user';


@Component({
? selector: 'app-user',
? templateUrl: './user.component.html',
? styleUrls: ['./user.component.css']
})
export class UserComponent {
? userList : User[] = [];
? roleList : Role[] = [];
? // this variable is get data from model
? userToModify : User = new User();
? // this variable determines wither we are in changing or creating new user
? creatingMode : boolean = true;


? constructor(private userService : UserService,
              private roleService : RoleService){
? ? this.getAllUsers();
? ? this.getAllRoles();
? }
? //Get All Users
? getAllUsers(){
? ? this.userService.getAll().subscribe((response : User[])=>{
? ? ? this.userList = response;
? ? });
? }
? //Get All Roles
? getAllRoles(){
? ? this.roleService.getAll().subscribe((response)=>{
? ? ? this.roleList = response;
? ? })
? }
? //Update User
? modifyUser(){
? ? this.userService.Update(this.userToModify).subscribe(()=>{
? ? ? alert("User Updated Successfully");
? ? ? window.location.reload();
? ? })
? }
? //Create new User
? CreateUser(){
? ? ? const newUser = {
? ? ? ? userName : this.userToModify.userName,
? ? ? ? lastName : this.userToModify.lastName,
? ? ? ? role : {
? ? ? ? ? id : this.userToModify.role.id,
? ? ? ? }
? ? ? };
? ? ? this.userService.Create(newUser).subscribe(()=>{
? ? ? ? alert("User Added Successfully");
? ? ? ? window.location.reload();
? ? ? });
? }
? //Delete User
? deleteUser(userId : string){
? ? if(confirm("Are you sure you want to delete this user !!!")){
? ? ? this.userService.Delete(userId).subscribe(()=>{
? ? ? ? alert("User Deleted Successfully");
? ? ? ? window.location.reload();
? ? ? });
? ? }
? }
? // function to verify the event
? openModel(user : User = new User()){
? ? if(user.id == ""){
? ? ? this.userToModify = new User();
? ? }else{
? ? ? this.creatingMode = false
? ? ? this.userToModify = user;
? ? }
? }
}        

In this example, we inject the UserService into the UserComponent constructor and utilize the getAll() method to fetch users data. The fetched data is stored in the userList variable for use in the component template.

Remember to adjust the service names, API endpoints, and component designs according to your specific application needs.

Conclusion

By adding a navigation bar, designing components, and consuming APIs from services, we enhance the functionality and user experience of our Angular application.

Step 5: Opening and Testing the Project

To open and test your Angular project, follow these steps:

1. Open your command prompt or terminal.

2. Navigate to the root directory of your Angular project.

3. Run the following command to start the development server:

ng serve -o        

This command will compile your Angular application and serve it locally on `https://localhost:4200`.

4. Open your web browser and visit `https://localhost:4200` to view your application.

5. Test the functionality of your application by interacting with different components, navigating through routes, and verifying that the data is displayed correctly.

- Verify that the navigation bar is visible and the routing between components is working as expected.

- Check if the data from APIs is fetched and displayed correctly in the respective components.

- Perform any other operations or functionalities that we have implemented in our application.


By testing your project, you can ensure that it functions correctly and meets your requirements. If you encounter any issues or errors, review your code and make necessary adjustments.

Note: Make sure you have the necessary backend services running (the Spring Boot application) to handle API requests and provide the expected data to the Angular frontend.

Keep the command prompt or terminal running to keep the development server active. You can make changes to your code and the server will automatically recompile and reflect the updates in the browser.

Now, let's take a closer look at our application in action. Below are some screenshots showcasing the different components, navigation, and data display. Please note that these screenshots provide a glimpse into the user interface and functionality of our application.

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image


Lastly you can download the complete code of this tutorial from?Repository Link.

Conclusion

In conclusion, we have successfully completed the journey of building a full-stack web application using Angular 15, Spring Boot 3, and MongoDB. Throughout this tutorial, we have covered various aspects of the development process, from setting up the backend with Spring Boot and MongoDB to creating the frontend with Angular.


We started by laying the foundation with the backend, implementing the necessary APIs, and connecting to the database. Then, we moved on to the frontend, where we designed and developed components, integrated Bootstrap for styling, and utilized Angular services to consume APIs and handle data operations.


By following this tutorial, you have gained a solid understanding of how to leverage the power of Angular and Spring Boot to create robust and efficient web applications. You have learned how to manage services, create components, implement routing, and interact with backend APIs.


Remember that this tutorial serves as a starting point, and there is always room for further enhancements and customization based on your specific requirements. You can explore additional features such as authentication, authorization, data validation, and error handling to make your application more secure and user-friendly.


We hope this tutorial has provided you with valuable insights and practical knowledge to embark on your own full-stack web development projects. Feel free to leverage the skills you have acquired here and continue expanding your expertise in building innovative and dynamic web applications.


Thank you for joining us on this journey, and we wish you all the best in your future endeavors as a full-stack developer. Happy coding!

Github?Web Site

Pranav Tallewar

Microservices | Full Stack Development | Technical Owner | Product Development

4 个月

Hello Imed, Appreciate detailed steps to create full stack app. However, during setting up database, I do not see steps for setting up mongo-db server.

回复
Zied Yahiaoui

BMS Cheif && Full Stack web developer

11 个月

Bonjour Imed: Disponible sur youtube ?

回复
Houssem Hammami

Master 2 Data science | Computer Science student at ENSI | AI | Passionate About Embedded Systems and Computer Graphics

1 年

very helpful

Wafa Neji

Technologue en informatique

1 年

Bravo Imed ??

Aicha Mohamed

Full Stack Developer ????|| Express.js || ReactJs || Node.js || MongoDB || JavaScript

1 年

Great work

要查看或添加评论,请登录

社区洞察

其他会员也浏览了