47 Creating Login and Signup Mutations
47 Creating Login and Signup Mutations
User authentication is the foundation of nearly every modern application. Security and efficiency in managing user identities are crucial, whether for a simple application or a complex one. In this 47th article of the series, I want to take a deep dive into how to build mutations for Login and Signup using GraphQL on Node.js, complete with sample code, simulations, and flow diagrams to make everything easier to understand.
Why Mutations?
Mutations in GraphQL are used to modify data on the server, making them a great fit for operations such as create (Signup) and update (Login—typically for authentication). Compared to a REST API, the GraphQL approach makes endpoints more elegant and explicit. This becomes especially valuable as an application grows and its requirements become more complex.
High-Level Architecture
Before diving into the code, let’s understand the architecture at a high level:
- Frontend: Sends the
loginandsignupmutations to the backend. - Backend: Provides the Schema and Resolvers for those mutations.
- Database: Stores user data with encrypted (hashed) passwords.
Let’s illustrate the process with the following flow diagram.
graph TD A[User] -->|Signup| B[GraphQL Mutation - signup] B --> C[Resolver] C --> D[Hash Password] D --> E[Store to Database] A2[User] -->|Login| F[GraphQL Mutation - login] F --> G[Resolver] G --> H[Verify Password] H --> I[Return JWT Token]
Designing the GraphQL Schema
Let’s start by designing the basic schema for the Login and Signup mutations.
1type Mutation {
2 signup(username: String!, password: String!): AuthPayload!
3 login(username: String!, password: String!): AuthPayload!
4}
5
6type AuthPayload {
7 token: String
8 user: User
9}
10
11type User {
12 id: ID!
13 username: String!
14}Explanation:
signupandloginreturn anAuthPayloadobject.AuthPayloadcontains the JWT token and the user data.- The mutations accept username and password explicitly.
Implementation Code in Node.js
Tech Stack:
- Node.js
- Express.js
- Apollo Server (GraphQL)
- bcrypt (for hashing passwords)
- jsonwebtoken (for generating JWTs)
Let’s assume we already have a connection to a MongoDB database.
1. User Model (MongoDB/Mongoose)
1// models/User.js
2const mongoose = require('mongoose');
3
4const userSchema = new mongoose.Schema({
5 username: { type: String, unique: true, required: true },
6 password: { type: String, required: true },
7});
8
9module.exports = mongoose.model('User', userSchema);2. Implementing the Resolvers
Let’s look at the code logic for signup and login.
1// resolvers.js
2const bcrypt = require('bcryptjs');
3const jwt = require('jsonwebtoken');
4const User = require('./models/User');
5
6const SECRET = 'MY_SUPER_SECRET_KEY';
7
8const resolvers = {
9 Mutation: {
10 signup: async (_, { username, password }) => {
11 // Check whether the user already exists
12 const existingUser = await User.findOne({ username });
13 if (existingUser) {
14 throw new Error('Username has been taken');
15 }
16
17 // Hash the password with bcrypt
18 const hashedPassword = await bcrypt.hash(password, 10);
19
20 // Create a new user
21 const user = new User({ username, password: hashedPassword });
22 await user.save();
23
24 // Generate a JWT token
25 const token = jwt.sign(
26 { userId: user.id, username: user.username },
27 SECRET,
28 { expiresIn: '7d' }
29 );
30
31 return {
32 token,
33 user
34 };
35 },
36
37 login: async (_, { username, password }) => {
38 const user = await User.findOne({ username });
39 if (!user) {
40 throw new Error('User not found');
41 }
42
43 const valid = await bcrypt.compare(password, user.password);
44 if (!valid) {
45 throw new Error('Invalid password');
46 }
47
48 const token = jwt.sign(
49 { userId: user.id, username: user.username },
50 SECRET,
51 { expiresIn: '7d' }
52 );
53
54 return {
55 token,
56 user
57 };
58 }
59 }
60};
61
62module.exports = resolvers;Simulating Requests and Responses
Let’s look at real examples of requests and responses for these mutations.
1. Signup
Query
1mutation {
2 signup(username: "johndoe", password: "supersecret") {
3 token
4 user {
5 id
6 username
7 }
8 }
9}Response
1{
2 "data": {
3 "signup": {
4 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
5 "user": {
6 "id": "649c22ebd58be255b9a38212",
7 "username": "johndoe"
8 }
9 }
10 }
11}2. Login
Query
1mutation {
2 login(username: "johndoe", password: "supersecret") {
3 token
4 user {
5 id
6 username
7 }
8 }
9}Response
1{
2 "data": {
3 "login": {
4 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
5 "user": {
6 "id": "649c22ebd58be255b9a38212",
7 "username": "johndoe"
8 }
9 }
10 }
11}Mutation vs REST Comparison
| Feature | GraphQL Mutation | REST API |
|---|---|---|
| Endpoint | A single “/graphql” endpoint | Many endpoints |
| Response Data | Tailored to the client’s request | Fixed/limited |
| API Evolution | Easy to extend | More rigid |
| Error Handling | Consistent via the GraphQL response | Depends on the implementation |
Best Practices
- Hash Passwords: Always hash passwords with
bcryptbefore storing them in the database. - JWT Expiry: Include an expiration date on the JWT token for security.
- Error Messages: Never expose sensitive error messages.
- Input Validation: Validate the username and password on the backend.
- Rate-Limit & Lockout: Implement rate limiting on the login endpoint to prevent brute-force attacks.
Potential Extensions
These mutations form the core foundation. There are many possible enhancements:
- Password Reset
- Email Verification
- Social Login
- Multi-Factor Authentication
Conclusion
Building login and signup mutations with GraphQL offers many advantages: efficiency, ease of extension, and more structured security. Clean code paired with a clear architecture strongly supports application scalability.
If you want to strengthen your application’s authentication system, understanding and implementing these two fundamental mutations is a very worthwhile investment. Happy experimenting, and don’t forget to add extra protection and validation in the next steps!
Further resources: