DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Updated on

Detail implementation of User Management

Below is a detailed implementation for user management, including user registration, login, profile management, and role-based access control using Next.js, NestJS, and GraphQL.

Backend (NestJS)

1. User Entity

// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  password: string;

  @Column()
  role: string;
}
Enter fullscreen mode Exit fullscreen mode

2. User Service

// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import * as bcrypt from 'bcrypt';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  async findOne(username: string): Promise<User | undefined> {
    return this.usersRepository.findOne({ where: { username } });
  }

  async create(username: string, password: string, role: string): Promise<User> {
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = this.usersRepository.create({ username, password: hashedPassword, role });
    return this.usersRepository.save(newUser);
  }

  async validateUser(username: string, password: string): Promise<User | null> {
    const user = await this.findOne(username);
    if (user && await bcrypt.compare(password, user.password)) {
      return user;
    }
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. User Resolver

// user.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { UserService } from './user.service';
import { User } from './user.entity';
import { UseGuards } from '@nestjs/common';
import { GqlAuthGuard } from './guards/gql-auth.guard';

@Resolver(() => User)
export class UserResolver {
  constructor(private userService: UserService) {}

  @Mutation(() => User)
  async register(
    @Args('username') username: string,
    @Args('password') password: string,
    @Args('role') role: string,
  ) {
    return this.userService.create(username, password, role);
  }

  @Mutation(() => String)
  async login(@Args('username') username: string, @Args('password') password: string) {
    const user = await this.userService.validateUser(username, password);
    if (user) {
      // Generate JWT token (implementation not shown here)
      return 'JWT_TOKEN';
    }
    throw new Error('Invalid credentials');
  }

  @Query(() => User)
  @UseGuards(GqlAuthGuard)
  async profile(@Args('username') username: string) {
    return this.userService.findOne(username);
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Authentication Guard

// gql-auth.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }
}
Enter fullscreen mode Exit fullscreen mode

5. GraphQL Schema

type User {
  id: ID!
  username: String!
  role: String!
}

type Query {
  profile(username: String!): User!
}

type Mutation {
  register(username: String!, password: String!, role: String!): User!
  login(username: String!, password: String!): String!
}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Apollo Client Setup

// apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql',
  cache: new InMemoryCache(),
});

export default client;
Enter fullscreen mode Exit fullscreen mode

2. Registration Form

// pages/register.js
import { useState } from 'react';
import { useMutation, gql } from '@apollo/client';
import { useRouter } from 'next/router';

const REGISTER_USER = gql`
  mutation Register($username: String!, $password: String!, $role: String!) {
    register(username: $username, password: $password, role: $role) {
      id
      username
    }
  }
`;

export default function Register() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [role, setRole] = useState('student');
  const [register] = useMutation(REGISTER_USER);
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await register({ variables: { username, password, role } });
      router.push('/login');
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} />
      <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <select value={role} onChange={(e) => setRole(e.target.value)}>
        <option value="student">Student</option>
        <option value="teacher">Teacher</option>
        <option value="admin">Admin</option>
      </select>
      <button type="submit">Register</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Login Form

// pages/login.js
import { useState } from 'react';
import { useMutation, gql } from '@apollo/client';
import { useRouter } from 'next/router';

const LOGIN_USER = gql`
  mutation Login($username: String!, $password: String!) {
    login(username: $username, password: $password)
  }
`;

export default function Login() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [login] = useMutation(LOGIN_USER);
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const { data } = await login({ variables: { username, password } });
      localStorage.setItem('token', data.login);
      router.push('/profile');
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} />
      <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <button type="submit">Login</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Profile Page

// pages/profile.js
import { useQuery, gql } from '@apollo/client';
import { useRouter } from 'next/router';
import { useEffect } from 'react';

const GET_PROFILE = gql`
  query GetProfile($username: String!) {
    profile(username: $username) {
      id
      username
      role
    }
  }
`;

export default function Profile() {
  const router = useRouter();
  const username = 'currentUsername'; // Replace with actual username retrieval logic
  const { loading, error, data } = useQuery(GET_PROFILE, {
    variables: { username },
  });

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (!token) {
      router.push('/login');
    }
  }, [router]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Profile</h1>
      <p>Username: {data.profile.username}</p>
      <p>Role: {data.profile.role}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Authentication Middleware

To protect the GraphQL API with JWT, you need to integrate a JWT strategy and middleware in NestJS. This includes creating a jwt.strategy.ts, updating user.service.ts to generate JWTs, and protecting routes using the GqlAuthGuard.

This outline provides a solid foundation for implementing user management with registration, login, profile management, and role-based access control. You can expand it further based on specific project requirements and security best practices.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content in generated by AI.

Top comments (0)