DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on

Detail implementation of User Management

Certainly! 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.

Disclaimer: This content in generated by AI.

Top comments (0)