Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
28 Jul 2025 · 6 min read ·Article 28 / 125
Go

28 Building a Resolver to Add a New User

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

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:

graphql
 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:

js
 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:

js
 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

js
 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:

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 ActionServer Response
Send the addUser mutation with dataServer receives the input, runs validation
Input missing or invalidServer returns error: “All fields are required.”
Email already registeredServer returns error: “User is already registered.”
Valid data, password >= 6 charsServer hashes the password, saves to MongoDB
User addedServer returns the new user data (without password) to the client

Best-Practice Tips

A few extra tips from my own experience:

ProblemBest Practice
Complex validationUse a library such as Joi or Zod
Plaintext passwordsNever return the password to the client
Race conditionsAdd a unique constraint at the database level
LoggingRecord new-user events for audit/security logs
Error handlingAlways 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! 🚀👨‍💻

Related Articles

💬 Comments