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)