28 Building a Resolver to Add a New User
28. Building a Resolver to Add a New User: A Complete and Practical Guide
Resolvers are the heart of a GraphQL API. Their job is to “connect” requests from the client to the backend business logic, including the database and other services. One of the most fundamental use cases in modern application development is adding a new user—whether for a SaaS app, an e-commerce platform, or a community site. In this 28th article of our GraphQL and backend engineering series, I’ll walk through, step by step, how to build a resolver that adds a new user. This tutorial comes with practical source code, request simulations, a data-flow diagram, and best-practice tips drawn from hands-on experience.
Why Is a User Resolver So Important?
Adding users is the entry point of almost every application. It’s not just about saving data to the database—the process involves validation, password hashing, error handling, and even notifications. With the resolver pattern, all of this can be customized to fit your needs while staying structured and easy to maintain.
User Schema and Environment Setup
Let’s assume we’re using Node.js, Express, and graphql, along with a MongoDB database using the Mongoose ODM.
Example GraphQL User schema:
1type User {
2 id: ID!
3 username: String!
4 email: String!
5 createdAt: String!
6}
7
8input AddUserInput {
9 username: String!
10 email: String!
11 password: String!
12}
13
14type Mutation {
15 addUser(input: AddUserInput!): User!
16}MongoDB Model with Mongoose
Before getting into the resolver, let’s set up the following Mongoose model:
1// models/User.js
2const mongoose = require('mongoose');
3
4const userSchema = new mongoose.Schema({
5 username: { type: String, unique: true },
6 email: { type: String, unique: true },
7 password: { type: String },
8 createdAt:{ type: Date, default: Date.now }
9});
10
11module.exports = mongoose.model('User', userSchema);A Functional Resolver: Step by Step
Let’s break the process down into the following steps.
1. Receive the Input
The resolver receives the input argument from the GraphQL mutation:
1mutation {
2 addUser(input: {
3 username: "johndoe",
4 email: "john@example.com",
5 password: "securepass123"
6 }) {
7 id
8 username
9 email
10 createdAt
11 }
12}2. Validate the Data
Perform a simple validation—check whether the username and email are unique, and that the password is at least 6 characters long.
3. Hash the Password
Use bcrypt to keep the password secure.
4. Save to the Database
Save the new record to MongoDB.
5. Return the User Data (Without the Password!)
The Resolver Structure
1// resolvers/userResolver.js
2const User = require('../models/User');
3const bcrypt = require('bcrypt');
4
5module.exports = {
6 Mutation: {
7 addUser: async (_, { input }) => {
8 const { username, email, password } = input;
9
10 // 1. Validate the input
11 if (!username || !email || !password)
12 throw new Error('All fields are required.');
13
14 if (password.length < 6)
15 throw new Error('Password must be at least 6 characters.');
16
17 // 2. Check for duplicate email/username
18 const existUser = await User.findOne({ $or: [ { email }, { username } ] });
19 if (existUser) throw new Error('User is already registered.');
20
21 // 3. Hash the password
22 const hashedPwd = await bcrypt.hash(password, 10);
23
24 // 4. Save the new user
25 const user = new User({
26 username,
27 email,
28 password: hashedPwd
29 });
30
31 await user.save();
32
33 // 5. Return the user without the password
34 return {
35 id: user._id,
36 username: user.username,
37 email: user.email,
38 createdAt: user.createdAt.toISOString()
39 };
40 }
41 }
42};Resolver Process Flow Diagram
Here’s a diagram of the data flow, from the user signing up to the data being saved, drawn with mermaid:
flowchart TD
A[Client Mengirim AddUser Mutation] --> B[GraphQL Server Menerima Input]
B --> C{Validasi Input}
C -- Tidak Valid --> D[Return Error Ke Client]
C -- Valid --> E[Cek Duplikasi di Database]
E -- User Ada --> D
E -- Tidak Ada --> F[Hash Password]
F --> G[Simpan User Baru ke DB]
G --> H[Kembalikan Data User]
H --> I[Client Mendapat Data User]
Simulation: A Conversation Between Client and Server
Let’s look at an illustrated dialogue between the client and the server:
| Client Action | Server Response |
|---|---|
| Send the addUser mutation with data | Server receives the input, runs validation |
| Input missing or invalid | Server returns error: “All fields are required.” |
| Email already registered | Server returns error: “User is already registered.” |
| Valid data, password >= 6 chars | Server hashes the password, saves to MongoDB |
| User added | Server returns the new user data (without password) to the client |
Best-Practice Tips
A few extra tips from my own experience:
| Problem | Best Practice |
|---|---|
| Complex validation | Use a library such as Joi or Zod |
| Plaintext passwords | Never return the password to the client |
| Race conditions | Add a unique constraint at the database level |
| Logging | Record new-user events for audit/security logs |
| Error handling | Always send friendly error messages |
Advanced Customization
In production, the addUser flow is usually extended further with:
- Email verification: Send a confirmation email to activate the account
- Rate limiting: Limit user creation per IP to prevent spam
- SSO integration: Add Google, Apple, and other SSO providers
- Async jobs: Send emails via a queue (e.g., using Bull or RabbitMQ)
Conclusion
Building a GraphQL resolver to add a user is a core foundation of modern backend development. With a modular approach—from input validation and password hashing to error handling—we can ensure a secure and well-structured user onboarding process.
Key takeaways from this article:
- Resolver = the bridge between GraphQL and your backend logic
- Always validate input and protect sensitive data
- Build a reusable pattern from the start.
- Extend it to match your business and security needs.
I hope the steps above serve as a reference for building a scalable, secure backend! Happy experimenting, and feel free to leave a comment if you have any questions or additional insights.
Further Reading:
Thanks for reading! 🚀👨💻