A Full-Stack Web App Using Angular and GraphQL: Adding User Registration Functionality (Part 4)
Tags: angular, development, web, ui
Published: 2024-03-04 02:25:05

---
title: A Full-Stack Web App Using Angular and GraphQL: Adding User Registration Functionality (Part 4)
published: true
date: 2024-02-28 12:59:36 UTC
tags: angular, development, web, ui
canonical_url: https://www.syncfusion.com/blogs/post/full-stack-app-angular-graphql-4.aspx
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u7u9xtgxdr7xy1duxuk5.png
---

Welcome to our journey of building a full-stack web application using Angular and GraphQL.

In the [previous article](https://www.syncfusion.com/blogs/post/full-stack-app-angular-graphql-3.aspx "Blog: A Full-Stack Web App Using Angular and GraphQL: Perform Edit, Delete, and Advanced Filtering (Part 3)") of this series, we learned how to add the edit and delete features to our application. We also configured the home page and provided the sort and filter options for the list of movies.

In this article, we will add the feature for user registration in our application.

## Adding the required model classes

First, let’s create a class named **UserRoles.cs** within the **Models** folder and define the user roles in our app.

```js
public static class UserRoles
{
    public const string Admin = "Admin";
    public const string User = "User";
}
```

This static class specifies the allowed user roles in our application.

Next, add another class named **UserRegistration.cs** in the **Models** folder with the following code.

```js
public class UserRegistration
{
        [Required]
        public string FirstName { get; set; }
        [Required]
        public string LastName { get; set; }
        [Required]
        public string Username { get; set; }
        [Required]
        [RegularExpression(@"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$")]
        public string Password { get; set; }
        [Required]
        [Compare("Password")]
        public string ConfirmPassword { get; set; }
        [Required]
        public string Gender { get; set; }
        public UserRegistration()
        {
            FirstName = string.Empty;
            LastName = string.Empty;
            Gender = string.Empty;
            Username = string.Empty;
            Password = string.Empty;
            ConfirmPassword = string.Empty;
        }
}
```

This class is used to capture user registration details. All the fields of this class are marked optional. The [**RegularExpression**] attribute applied to the **Password** field is used to enforce password complexity rules: the password should have a minimum of 8 characters, at least 1 uppercase letter, 1 lowercase letter, and 1 number.

Next, add another class named **RegistrationResponse.cs** inside the **Models** folder.

```js
public class RegistrationResponse
{
    public bool IsRegistrationSuccess { get; set; }
    public string? ErrorMessage { get; set; }
}
```

This class returns the registration response to the client.

## Create the IUser interface

In the **MovieApp\Interfaces** folder, add a new file called **IUser.cs** with the following method declarations.

```js
public interface IUser
{
    Task<bool> RegisterUser(UserMaster userData);
    Task<bool> IsUserExists(int userId);
    bool CheckUserNameAvailability(string username);
}
```

## Creating a UserDataAccessLayer for the application

Add a class named **UserDataAccessLayer.cs** inside the **MovieApp\DataAccess** folder with the following code.

```js
public class UserDataAccessLayer: IUser
{
    readonly MovieDbContext _dbContext;
    public UserDataAccessLayer(IDbContextFactory<MovieDbContext> dbContext)
    {
        _dbContext = dbContext.CreateDbContext();
    }
    public async Task<bool> IsUserExists(int userId)
    {
        UserMaster? user = await _dbContext.UserMasters.FirstOrDefaultAsync(x => x.UserId == userId);
        return user != null;
    }
    public async Task<bool> RegisterUser(UserMaster userData)
    {
        bool isUserNameAvailable = CheckUserNameAvailability(userData.Username);
        try
        {
            if (isUserNameAvailable)
            {
                await _dbContext.UserMasters.AddAsync(userData);
                await _dbContext.SaveChangesAsync();
                return true;
            }
            else
            {
                return false;
            }
        }
        catch
        {
            throw;
        }
    }
    public bool CheckUserNameAvailability(string userName)
    {
        string? user = _dbContext.UserMasters.FirstOrDefault(x => x.Username == userName)?.ToString();
        return user == null;
    }
}
```

We have implemented the **IUser** interface and added the definition for all the required methods:

- The **IsUserExists** method checks whether a user exists in the database based on the provided user ID.
- The **RegisterUser** method is used to register a new user. It first checks if the username is available, and if it is, it adds the new user to the **UserMasters** **DbSet** and saves the changes to the database.
- The **CheckUserNameAvailability** method checks whether a username is available in the database. It returns **true** if the username is found, indicating it is available.

## Add a GraphQL mutation resolver for authentication

In the **MovieApp\GraphQL** folder, add a class named **AuthMutationResolver.cs**.

```js
[ExtendObjectType(typeof(MovieMutationResolver))]
public class AuthMutationResolver
{
    readonly IUser _userService;
    public AuthMutationResolver(IUser userService)
    {
        _userService = userService;
    }
    [GraphQLDescription("Register a new user.")]
    public async Task<RegistrationResponse> UserRegistration([FromBody] UserRegistration registrationData)
    {
        UserMaster user = new()
        {
            FirstName = registrationData.FirstName,
            LastName = registrationData.LastName,
            Username = registrationData.Username,
            Password = registrationData.Password,
            Gender = registrationData.Gender,
            UserTypeName = UserRoles.User
        };
        bool userRegistrationStatus = await _userService.RegisterUser(user);
        if (userRegistrationStatus)
        {
            return new RegistrationResponse { IsRegistrationSuccess = true };
        }
        else
        {
            return new RegistrationResponse { IsRegistrationSuccess = false, ErrorMessage = "This username is not available." };
        }
    }
}
```

The **AuthMutationResolver** class allows registering a new user in the app, using the **IUser** service to interact with the user data.

The **UserRegistration** asynchronous method is used to handle the user registration. It creates a new **UserMaster** object from the provided registration data and then uses the **\userService** to register the new user. A **RegistrationResponse** instance is returned with **IsRegistrationSuccess** set to true if the registration is successful. If the registration was unsuccessful, a **RegistrationResponse** instance is returned with **IsRegistrationSuccess** set to **false** and an error message indicating that the username is unavailable.

The **ExtendObjectType** attribute tells the GraphQL schema that the **AuthMutationResolver** class is extending the **MovieMutationResolver** type. This means that the fields and methods defined in **AuthMutationResolver** will be added to the **MovieMutationResolver** type in the GraphQL schema. This is a feature of the **Hot** **Chocolate** **library** that allows for a more modular and organized structure of your GraphQL schema.

**Note:** GraphQL restricts the creation of more than one mutation type.

## Register the mutation resolver

As a new mutation resolver has been added, it must be registered in our middleware. Update the **Program.cs** file with the following code.

```js
builder.Services.AddGraphQLServer()
    .AddQueryType<MovieQueryResolver>()
    .AddMutationType<MovieMutationResolver>()
    .AddTypeExtension<AuthMutationResolver>()
    .AddFiltering()
    .AddErrorFilter(error =>
    {
        return error;
    });
```

We use the **AddTypeExtension** method to register the new mutation resolver type **MovieMutationResolver**. Then, register the transient lifetime of the **IUser** service using the following code.

```js
builder.Services.AddTransient<IUser, UserDataAccessLayer>();
```

We are done with the server configuration. Let’s move to the client side of the app.

## Add the GraphQL query and mutation

Add the following GraphQL mutation in the **src\app\GraphQL\mutation.ts** file. It will allow us to register a new user in the app.

```js
export const REGISTER_USER = gql`
  mutation register($registrationData: UserRegistrationInput!) {
    userRegistration(registrationData: $registrationData) {
      isRegistrationSuccess
    }
  }
`;
```

## Create the client-side model

Create a new file named **userRegistration.ts** under the **src\app\models** folder and include the following code.

```js
export interface UserRegistration {
  firstName: string;
  lastName: string;
  username: string;
  password: string;
  confirmPassword: string;
  gender: string;
}
export interface RegistrationResponse {
  isRegistrationSuccess: boolean;
  errorMessage: string;
}
export type RegistrationType = {
  userRegistration: RegistrationResponse;
};
```

In the previous code example:

- The **UserRegistration** interface is used to capture the details a user provides during registration.
- The **RegistrationResponse** is used to provide feedback on whether the registration was successful or not.
- The **RegistrationType** is used to define the structure of an object that includes a **RegistrationResponse**.

Then, create a new file named **userRegistrationForm.ts** in the **Models** folder and add the following code to it.

```js
import { FormControl } from '@angular/forms';
export interface UserRegistrationForm {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
  userName: FormControl<string>;
  password: FormControl<string>;
  confirmPassword: FormControl<string>;
  gender: FormControl<string>;
}
```

This interface creates a strongly typed reactive form for capturing user registration details.

## Create the GraphQL service

Run the following command in the **ClientApp** folder to generate a service file.

```
ng g s services\registration
```

Add the following code to the **registration.service.ts** file.

```js
import { Injectable } from '@angular/core';
import { Mutation } from 'apollo-angular';
import { REGISTER_USER } from '../GraphQL/mutation';
import { RegistrationType } from '../models/userRegistration';
@Injectable({
  providedIn: 'root',
})
export class RegistrationService extends Mutation<RegistrationType> {
  document = REGISTER_USER;
}
```

This service is used to register a new user using the GraphQL mutation.

## Create the custom form validator service

Let’s create a custom form validator service. First, run the following command to generate a service file.

```
ng g s services\custom-form-validator
```

Then, add the following code to the **custom-form-validator.service.ts** file.

```js
import { Injectable } from '@angular/core';
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
@Injectable({
  providedIn: 'root',
})
export class CustomFormValidatorService {
  passwordPatternValidator(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if (!control.value) {
        return null;
      }
      const regex = new RegExp('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$');
      const validPattern = regex.test(control.value);
      return validPattern ? null : { invalidPassword: true };
    };
  }
  matchPasswordValidator(password: string, confirmPassword: string) {
    return (formGroup: AbstractControl): ValidationErrors | null => {
      const passwordControl = formGroup.get(password);
      const confirmPasswordControl = formGroup.get(confirmPassword);
      if (!passwordControl || !confirmPasswordControl) {
        return null;
      }
      if (
        confirmPasswordControl.errors &&
        !confirmPasswordControl.errors['passwordMismatch']
      ) {
        return null;
      }
      if (passwordControl.value !== confirmPasswordControl.value) {
        confirmPasswordControl.setErrors({ passwordMismatch: true });
        return { passwordMismatch: true };
      } else {
        confirmPasswordControl.setErrors(null);
        return null;
      }
    };
  }
}
```

The custom form validator service provides two methods for validating form inputs, specifically for password fields:

- The **passwordPatternValidator** method returns a **ValidatorFn** function that takes an **AbstractControl** as an argument. The **AbstractControl** represents a specific form control, in this case, a password field. The function checks if the control’s value matches a specific regular expression pattern. The pattern requires at least one uppercase letter, one lowercase letter, one number, and a minimum length of 8 characters. If the control’s value doesn’t match this pattern, the function returns an object with the property **invalidPassword** set to **true**. If the control’s value is **null** or matches the pattern, the function returns null, indicating no validation errors.
- The **matchPasswordValidator** method returns a **ValidatorFn** function that takes an **AbstractControl** as an argument. The **AbstractControl**, in this case, represents a form group that includes the password and confirm password fields. The function checks if the values of these two fields match. If they don’t match, the function sets a **passwordMismatch** error on the confirm password control and returns an object with a **passwordMismatch** property set to **true**. If the values match, the function clears any errors on the confirm password control and returns null, indicating no validation errors.

## Create the user registration component

Run the following command to create the user registration component.

```
ng g c components\user-registration
```

Update the **UserRegistrationComponent** class in the **src\app\components\user-registration\user-registration.component.ts** file as follows.

```js
export class UserRegistrationComponent {
  protected userRegistrationForm!: FormGroup<UserRegistrationForm>;
  private destroyed$ = new ReplaySubject<void>(1);
  protected submitted = false;
  public data = ['Male', 'Female'];
  constructor(
    private readonly router: Router,
    private readonly formBuilder: NonNullableFormBuilder,
    private readonly customFormValidator: CustomFormValidatorService,
    private readonly registrationService: RegistrationService
  ) {
    this.initializeForm();
  }
  private initializeForm(): void {
    this.userRegistrationForm = this.formBuilder.group(
      {
        firstName: this.formBuilder.control('', Validators.required),
        lastName: this.formBuilder.control('', Validators.required),
        userName: this.formBuilder.control('', Validators.required),
        password: this.formBuilder.control('', [
          Validators.required,
          this.customFormValidator.passwordPatternValidator(),
        ]),
        confirmPassword: this.formBuilder.control('', Validators.required),
        gender: this.formBuilder.control('', Validators.required),
      },
      {
        validators: [
          this.customFormValidator.matchPasswordValidator(
            'password',
            'confirmPassword'
          ),
        ],
      }
    );
  }
}
```

The **initializeForm** method is called in the constructor to initialize the **userRegistrationForm**. It creates a form group with form controls for the first name, last name, username, password, confirm password, and gender. Each form control is initialized with an empty string and is required.

The password form control has an additional custom validator, the **passwordPatternValidator,** that validates the password pattern. The form group has a custom validator, **matchPasswordValidator** , that ensures the password and confirms that the password fields match.

Add the following functions to the **UserRegistrationComponent** class.

```js
registerUser(): void {
this.submitted = true;
const userRegistrationData: UserRegistration = {
  firstName: this.userRegistrationForm.value.firstName ?? '',
  lastName: this.userRegistrationForm.value.lastName ?? '',
  username: this.userRegistrationForm.value.userName ?? '',
  password: this.userRegistrationForm.value.password ?? '',
  confirmPassword: this.userRegistrationForm.value.confirmPassword ?? '',
  gender: this.userRegistrationForm.value.gender ?? '',
};
if (this.userRegistrationForm.valid) {
  this.registrationService
    .mutate({
      registrationData: userRegistrationData,
    })
    .pipe(takeUntil(this.destroyed$))
    .subscribe({
      next: (response) => {
        if (response.data?.userRegistration.isRegistrationSuccess) {
          ToastUtility.show({
            content: 'User registration successful.',
            position: { X: 'Right', Y: 'Top' },
            cssClass: 'e-toast-success',
          });
          this. router.navigate(['/']);
        } else {
          this.userRegistrationForm.controls.userName.setErrors({
            userNameNotAvailable: true,
          });
          console.error(
            'Error occurred during registration: ',
            response.data?.userRegistration.errorMessage
          );
        }
      },
    });
 }
}

get registrationFormControl() {
    return this.userRegistrationForm.controls;
}

ngOnDestroy() {
    this.destroyed$.next();
    this.destroyed$.complete();
}
```

The **registerUser** method is called to register a new user. It first sets the submitted property to **true** , which triggers validation messages in the template. Then, it creates a **userRegistrationData** object from the form values. The **nullish** **coalescing** **operator** **(??)** ensures that the object’s properties are **not null** or **undefined**.

If the form is **valid** , it calls the **mutate** method on the **registrationService** to register the new user. If the registration is **successful** , it displays a success toast notification and navigates to the root route. If the registration is **unsuccessful** , an error is set on the username form control indicating that the username is unavailable, and an error message is logged to the console.

The **registrationFormControl** returns the controls of the **userRegistrationForm**. This is used to access the form controls in the template.

Add the following code to the **user-registration.component.html** file.

```xml
<div class="row justify-content-center">
 <div class="col-md-6 col-lg-6 col-sm-12">
  <div class="title-container p-2 d-flex align-items-center justify-content-between">
   <h2 class="m-0">User Registration</h2>
   <div>
    <span class="mat-h4">Already Registered? </span>
    <button ejs-button cssClass="e-primary" [routerLink]="['/login']"> Login </button>
   </div>
  </div>
  <div class="e-card">
   <div class="e-card-content row g-0">
    <form [formGroup]="userRegistrationForm" (ngSubmit)="registerUser()">
     <div class="e-input-section">
      <ejs-textbox
           placeholder="First name"
           cssClass="e-outline"
           floatLabelType="Auto"
           formControlName="firstName"></ejs-textbox>
     </div>
     <div *ngIf="(registrationFormControl.firstName.touched || submitted) && registrationFormControl.firstName.errors?.['required']" class="e-error"> First Name is required. </div>
     <div class="e-input-section">
      <ejs-textbox
           placeholder="Last Name"
           cssClass="e-outline"
           floatLabelType="Auto"
           formControlName="lastName"></ejs-textbox>
     </div>
     <div *ngIf="(registrationFormControl.lastName.touched || submitted) && registrationFormControl.lastName.errors?.['required']" class="e-error"> Last Name is required. </div>
     <div class="e-input-section">
      <ejs-textbox
           placeholder="User Name"
           cssClass="e-outline"
           floatLabelType="Auto"
           formControlName="userName"></ejs-textbox>
     </div>
     <div *ngIf="(registrationFormControl.userName.touched || submitted) && registrationFormControl.userName.errors?.['required']" class="e-error" > Usern Name is required. </div>
     <div *ngIf="(registrationFormControl.userName.touched || submitted) && registrationFormControl.userName.errors?.['userNameNotAvailable']" class="e-error" > Usern Name is not available. </div>
     <div class="e-input-section">
      <ejs-textbox
           type="password"
           placeholder="Password"
           cssClass="e-outline"
           floatLabelType="Auto"
           formControlName="password"></ejs-textbox>
     </div>
     <div *ngIf="(registrationFormControl.password.touched || submitted) && registrationFormControl.password.errors?.['required']" class="e-error" > Password is required. </div>
     <div *ngIf="registrationFormControl.password.touched && registrationFormControl.password.errors?.['invalidPassword']" class="e-error"> Password should have minimum 8 characters, at least 1 uppercase letter, 1 lowercase letter, and 1 number. </div>
     <div class="e-input-section">
      <ejs-textbox
           type="password"
           placeholder="Confirm Password"
           cssClass="e-outline"
           floatLabelType="Auto"
           formControlName="confirmPassword"></ejs-textbox>
     </div>
     <div *ngIf="(registrationFormControl.confirmPassword.touched || submitted) && registrationFormControl.confirmPassword.errors?.['required']" class="e-error"> Confirm password is required.</div>
     <div *ngIf=" registrationFormControl.confirmPassword.touched && registrationFormControl.confirmPassword.errors?.['passwordMismatch']" class="e-error"> Passwords do not match. </div>
     <div class="e-input-section">
      <ejs-dropdownlist
           [dataSource]="data"
           placeholder="Select gender"
           cssClass="e-outline"
           floatLabelType="Auto"
           formControlName="gender"></ejs-dropdownlist>
     </div>
     <div *ngIf="(registrationFormControl.gender.touched || submitted) && registrationFormControl.gender.errors?.['required']" class="e-error"> Gender is required.</div>
     <div class="e-card-actions d-flex justify-content-end">
      <button type="submit" ejs-button cssClass="e-info">Register</button>
     </div>
    </form>
   </div>
  </div>
 </div>
</div>
```

The form element represents the user registration form. The **formGroup** attribute is bound to the **userRegistrationForm** property, and the **ngSubmit** event is bound to the **registerUser** method. The **ejs-textbox** components represent the form controls. The **formControlName** attribute binds each **ejs-textbox** to a form control in the **userRegistrationForm**.

On selecting the form controls or when the form is submitted, it will validate the details. If anything in the form control is empty, it will display an error message. If the password provided in the password form control is not in a valid pattern, then it will display an **Invalid Password** error message. If the password provided in the **confirm** **password** form control is wrong, it will display a **Password**  **Mismatch** error message. If the username form control is empty, it will display a **Username** **is** **Not** **A** **vailable** error message.

The button element with the **type=”submit”** attribute is used to submit the form. The **ejs-button** directive is used to style the button.

The **ejs-button** directive with the **routerLink** attribute is used to navigate to the /**login** route, where a user can log in.

**Note:**

- Make sure you add the import for the **ReactiveFormsModule** in the **app.module.ts** file.
- The login component and the corresponding route will be created in the next part of this article. However, we have added a navigation link to the login page for consistency.

## Configure app routing

Finally, open the **src\app\app-routing.module.ts** file and add the route for the **UserRegistration** component under the **appRoutes** array.

```js
const appRoutes: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' },
  { path: 'register', component: UserRegistrationComponent },
  // Existing code
];
```

## Execution demo

After executing the previous code examples, we will get output like in the following image. ![Implementing User Registration in a Full-Stack Web App with Angular and GraphQL](https://www.syncfusion.com/blogs/wp-content/uploads/2024/02/Implementing-User-Registration-in-a-Full-Stack-Web-App-with-Angular-and-GraphQL.gif)

## GitHub reference

For more details, refer to the complete source code for the [full-stack web app with Angular and GraphQL](https://github.com/SyncfusionExamples/full-stack-web-app-using-angular-and-graphql "Full-Stack web app with Angular and GraphQL GitHub demo") on GitHub.

## Summary

Thanks for reading!  In this article, we learned how to add the user registration feature to our full-stack web app using Angular and GraphQL.

In the next article of this series, we’ll learn to implement the login functionality and add role-based authorization to our app.

Whether you’re already a valued Syncfusion user or new to our platform, we extend an invitation to explore our [Angular components](https://www.syncfusion.com/angular-components "Angular components") with the convenience of a [complimentary trial](https://www.syncfusion.com/downloads/angular "Get free evaluation of the Essential Studio for Angular"). This trial allows you to experience the full potential of our components to enhance your app’s user interface and functionality.

Our dedicated support system is readily available if you need guidance or have any inquiries. Contact us through our [support forum](https://www.syncfusion.com/forums "Syncfusion Support Forum"), [support portal](https://support.syncfusion.com/ "Syncfusion Support Portal"), or [feedback portal](https://www.syncfusion.com/feedback/angular "Syncfusion Feedback Portal"). Your success is our priority, and we’re always delighted to assist you on your development journey!

## Related blogs

- [A Full-Stack Web App Using Angular and GraphQL: Part 1](https://www.syncfusion.com/blogs/post/full-stack-web-app-angular-graphql.aspx "Blog: A Full-Stack Web App Using Angular and GraphQL: Part 1")
- [A Full-Stack Web App Using Angular and GraphQL: Data Fetching and Manipulation (Part 2)](https://www.syncfusion.com/blogs/post/full-stack-app-angular-graphql-2.aspx "Blog: A Full-Stack Web App Using Angular and GraphQL: Data Fetching and Manipulation (Part 2)")
- [A Full-Stack Web App Using Angular and GraphQL: Perform Edit, Delete, and Advanced Filtering (Part 3)](https://www.syncfusion.com/blogs/post/full-stack-app-angular-graphql-3.aspx "Blog: A Full-Stack Web App Using Angular and GraphQL: Perform Edit, Delete, and Advanced Filtering (Part 3)")
- [What’s New in Angular 17 and for Syncfusion Angular Components](https://www.syncfusion.com/blogs/post/whats-new-angular-17-syncfusion.aspx "Blog: What’s New in Angular 17 and for Syncfusion Angular Components")