How to Implement SSR in Next.js: Complete Guide
Learn how to implement SSR in Next.js with this 2025 guide. Boost SEO, speed, and Google rankings with our step-by-step Next.js SSR tutorial.
When you think of APIs, REST usually comes to mind: multiple endpoints like /api/posts, /api/posts/:id, or /api/posts/:id/edit. But with GraphQL, you don’t need several endpoints - a single /graphql endpoint is enough, and the client specifies exactly what data it needs. This flexibility makes GraphQL a powerful alternative for modern applications. In this guide, we’ll walk through how to build a CRUD GraphQL API with Next.js App Router using only the official GraphQL package - no Apollo, no Yoga. You’ll learn how to set up a schema, create resolvers, and implement a clean /graphql route, along with a simple UI for creating, reading, updating, and deleting posts. If you’ve been searching for a straightforward Next.js GraphQL tutorial, this step-by-step walkthrough will give you everything you need to get started.
For example, instead of sending multiple REST requests, you can send one GraphQL query:
query {
posts {
id
title
content
}
}
The server returns exactly that data, nothing more, nothing less.
In this, we’ll build a CRUD (Create, Read, Update, Delete) GraphQL API inside a Next.js App Router. We'll use the official graphql package only - no Apollo, no Yoga. Data will be stored in memory (for demo purpose).
We will use typescript here,
npx create-next-app@latest next-graphql-crud --typescript
cd next-graphql-crud
We only need one dependency: the official graphql library.
npm install graphql
Create a new folder called lib/ inside root directory, create a file called schema.ts.
// lib/schema.ts
import { buildSchema } from "graphql";
// schema
export const schema = buildSchema(`
type Post {
id: ID!
title: String!
content: String!
}
type Query {
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createPost(title: String!, content: String!): Post!
updatePost(id: ID!, title: String, content: String): Post!
deletePost(id: ID!): Boolean!
}
`);
type Post = { id: string; title: string; content: string };
const posts: Post[] = [];
// Resolvers
export const root = {
posts: () => posts,
post: ({ id }: { id: string }) => posts.find((p) => p.id === id),
createPost: ({ title, content }: { title: string; content: string }) => {
const newPost = { id: String(posts.length + 1), title, content };
posts.push(newPost);
return newPost;
},
updatePost: ({ id, title, content }: { id: string; title?: string; content?: string }) => {
const post = posts.find((p) => p.id === id);
if (!post) throw new Error("Post not found");
if (title !== undefined) post.title = title;
if (content !== undefined) post.content = content;
return post;
},
deletePost: ({ id }: { id: string }) => {
const index = posts.findIndex((p) => p.id === id);
if (index === -1) return false;
posts.splice(index, 1);
return true;
},
};
In above code we defined,
A Post type
Queries
Mutations (createPost, updatePost, deletePost)
Simple in-memory storage
Next.js App Router uses route handlers inside app/api/....
Create a file: app/api/graphql/route.ts.
// app/api/graphql/route.ts
import { schema, root } from "../../../../lib/schema";
import { graphql } from "graphql";
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const { query, variables } = await req.json();
const result = await graphql({
schema,
source: query,
rootValue: root,
variableValues: variables,
});
return NextResponse.json(result);
}
Our GraphQL endpoint is now available at:
http://localhost:3000/api/graphql
Add below code to app/page.tsx
. This will include:
Fetch posts
Allow creating new posts
Edit existing posts
Delete posts
// app/page.tsx
"use client";
import { useState, useEffect } from "react";
type Post = { id: string; title: string; content: string };
export default function Home() {
const [posts, setPosts] = useState<Post[]>([]);
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [editing, setEditing] = useState<Post | null>(null);
// Fetch all posts
async function fetchPosts() {
const res = await fetch("/api/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `query { posts { id title content } }`,
}),
});
const json = await res.json();
console.log("GraphQL response:", json);
if (json.data?.posts) {
setPosts(json.data.posts);
} else {
setPosts([]);
}
}
// Create or Update post
async function createOrUpdatePost() {
const query = editing
? `mutation Update($id: ID!, $title: String, $content: String) {
updatePost(id: $id, title: $title, content: $content) {
id title content
}
}`
: `mutation Create($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id title content
}
}`;
const variables = editing
? { id: editing.id, title, content }
: { title, content };
await fetch("/api/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
});
setTitle("");
setContent("");
setEditing(null);
fetchPosts();
}
// Delete post
async function deletePost(id: string) {
await fetch("/api/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `mutation Delete($id: ID!) { deletePost(id: $id) }`,
variables: { id },
}),
});
fetchPosts();
}
useEffect(() => {
fetchPosts();
}, []);
return (
<div className="p-6 space-y-4">
<h1 className="text-2xl font-bold">Posts CRUD (GraphQL API)</h1>
{/* Form */}
<div className="space-y-2 w-[60%]">
<div className="flex gap-2">
<input
className="border p-2 rounded w-1/2"
placeholder="title"
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
className="border p-2 rounded w-1/2"
placeholder="description"
id="description"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</div>
</div>
<button
className="bg-blue-500 text-white px-4 py-2 rounded cursor-pointer"
onClick={createOrUpdatePost}
>
{editing ? "Update Post" : "Create Post"}
</button>
{/* Posts List */}
<div className="space-y-3 mt-4">
{posts.map((post) => (
<div
key={post.id}
className="border rounded p-3 flex justify-between items-center"
>
<div>
<h2 className="font-semibold">{post.title}</h2>
<p className="text-sm">{post.content}</p>
</div>
<div className="space-x-2">
<button
className="px-3 py-1 bg-yellow-400 rounded cursor-pointer"
onClick={() => {
setEditing(post);
setTitle(post.title);
setContent(post.content);
}}
>
Edit
</button>
<button
className="px-3 py-1 bg-red-500 text-white rounded cursor-pointer"
onClick={() => deletePost(post.id)}
>
Delete
</button>
</div>
</div>
))}
</div>
</div>
);
}
Here’s what happens,
Create → mutation createPost(title, content)
Read → query posts
Update → mutation updatePost(id, title, content)
Delete → mutation deletePost(id)
All are handled by the single /api/graphql
endpoint.
Now run below command in terminal,
npm run dev
Here is how our UI will look,
Add new post
Update post
Delete Post
Building a scalable GraphQL API or a production-ready Next.js application can get tricky, especially when you need to handle advanced features like authentication, database integration, or performance optimization. That’s where Prishusoft comes in. With years of expertise in Next.js, GraphQL, and modern web development, our team delivers tailor-made solutions for startups, enterprises, and everything in between. Whether you’re looking to build a custom CRUD GraphQL API, migrate an existing REST API to GraphQL, or develop a full-featured Next.js app, we’ve got you covered.
Ready to bring your project to life? Contact Prishusoft today for professional development services that save you time, reduce costs, and accelerate your product launch.
Learn how to implement SSR in Next.js with this 2025 guide. Boost SEO, speed, and Google rankings with our step-by-step Next.js SSR tutorial.
Discover how to harness the power of Supabase with Next.js to create modern, scalable web applications. Learn to set up seamless backend and frontend integration, making your next project faster and more efficient.
Learn how to build an Electron desktop app with Next.js without using Nextron. Step-by-step tutorial with setup, scripts, and code examples for creating a cross-platform desktop app.
Get in touch with Prishusoft – your trusted partner for custom software development. Whether you need a powerful web application or a sleek mobile app, our expert team is here to turn your ideas into reality.