DEV Community

Felipe Cruz Bastos
Felipe Cruz Bastos

Posted on

Migrating a Simple CRUD API from REST to GraphQL

Hey everyone 👋

I first started learning GraphQL back in 2020, and since then, I’ve used it in two real projects during my career as a Software Engineer. At first, it looked too different from the REST style I was used to, but it didn’t take long to see its strengths, especially when dealing with flexible UIs.

If you're already familiar with REST and wondering how it maps to GraphQL in a CRUD scenario, this post is for you.

Let’s migrate a basic User API (Create, Read, Update, Delete) from REST to GraphQL, and see what changes.

👷 The REST Setup

Here’s a classic REST API with Node.js + Express:

// GET /users
app.get('/users', async (req, res) => {
  const users = await db.users.find();
  res.json(users);
});

// GET /users/:id
app.get('/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

// POST /users
app.post('/users', async (req, res) => {
  const newUser = await db.users.insert(req.body);
  res.status(201).json(newUser);
});

// PUT /users/:id
app.put('/users/:id', async (req, res) => {
  const updated = await db.users.update(req.params.id, req.body);
  res.json(updated);
});

// DELETE /users/:id
app.delete('/users/:id', async (req, res) => {
  await db.users.delete(req.params.id);
  res.status(204).send();
});

Enter fullscreen mode Exit fullscreen mode

It works well, but you usually need to version your routes (/api/v1/...), validate everything manually, and deal with over/underfetching on the frontend.

⚡ The GraphQL Version

Instead of many endpoints, GraphQL has one — and you control everything through queries and mutations.

🔸 Schema Definition

type Query {
  users: [User!]!
  user(id: ID!): User
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

type User {
  id: ID!
  name: String!
  email: String!
}

input CreateUserInput {
  name: String!
  email: String!
}

input UpdateUserInput {
  name: String
  email: String
}

Enter fullscreen mode Exit fullscreen mode

🔍 Queries (Read)
All users:

query {
  users {
    id
    name
    email
  }
}

Enter fullscreen mode Exit fullscreen mode

Single user by ID:

query {
  user(id: 1) {
    name
    email
  }
}

Enter fullscreen mode Exit fullscreen mode

✏️ Mutations (Write)
Create a user:

mutation {
  createUser(input: { name: "Felipe", email: "felipe@example.com" }) {
    id
    name
  }
}

Enter fullscreen mode Exit fullscreen mode

Update a user:

mutation {
  updateUser(id: 1, input: { name: "Felipe Updated" }) {
    id
    name
  }
}

Enter fullscreen mode Exit fullscreen mode

Delete a user:

mutation {
  deleteUser(id: 1)
}

Enter fullscreen mode Exit fullscreen mode

🛠️ Example Resolver Setup (Apollo Server)

const resolvers = {
  Query: {
    users: (_, __, { db }) => db.users.find(),
    user: (_, { id }, { db }) => db.users.findById(id),
  },
  Mutation: {
    createUser: (_, { input }, { db }) => db.users.insert(input),
    updateUser: (_, { id, input }, { db }) => db.users.update(id, input),
    deleteUser: async (_, { id }, { db }) => {
      await db.users.delete(id);
      return true;
    },
  },
};

Enter fullscreen mode Exit fullscreen mode

✅ Why Migrate?
Here’s what I liked after migrating REST endpoints to GraphQL:

One single endpoint for everything

Flexible queries on the frontend — no under/overfetch

Strong typing from the schema

Easier to evolve without versioning routes

Of course, GraphQL adds some learning curve and server-side complexity. But in return, the client experience gets much cleaner, especially for frontend-heavy applications.

Top comments (0)