Back to Blog
6 min read

How to Create a Fake REST API in 5 Minutes (Complete Tutorial)

Step-by-step guide to creating a fake REST API for testing and development. Includes JSON Server, Mocky, and AI-powered solutions with code examples.

TutorialREST APIMock DataGetting Started

How to Create a Fake REST API in 5 Minutes

Need a REST API but don't have a backend? Whether you're learning, prototyping, or waiting for your backend team, this tutorial shows you three ways to create a fake REST API in under 5 minutes.

Why Use a Fake REST API?

  • 🎓 Learning: Practice frontend development without setting up a backend
  • 🚀 Prototyping: Build demos and MVPs quickly
  • 🧪 Testing: Test your frontend with consistent data
  • Development: Don't wait for backend teams

Best for: Production-quality fake APIs with realistic data

Setup time: 2 minutes

Step 1: Install the SDK

npm install @symulate/sdk

Step 2: Define Your Endpoints

Create api/posts.ts:

import { defineEndpoint, m, type Infer } from '@symulate/sdk'

// Define schema
const PostSchema = m.object({
  id: m.uuid(),
  title: m.lorem.sentence(),
  body: m.lorem.paragraphs({ min: 2, max: 4 }),
  author: m.person.fullName(),
  publishedAt: m.date({ max: 'now' }),
  likes: m.number({ min: 0, max: 1000 }),
  comments: m.number({ min: 0, max: 50 })
})

// Infer TypeScript type
type Post = Infer<typeof PostSchema>

// Get all posts
export const getPosts = defineEndpoint<Post[]>({
  path: '/api/posts',
  method: 'GET',
  schema: PostSchema,
  mock: {
    count: 15 // Generate 15 posts
  }
})

// Get single post
export const getPost = defineEndpoint<Post>({
  path: '/api/posts/:id',
  method: 'GET',
  params: [
    {
      name: 'id',
      location: 'path',
      required: true,
      schema: m.uuid()
    }
  ],
  schema: PostSchema
})

// Create post
export const createPost = defineEndpoint<Post>({
  path: '/api/posts',
  method: 'POST',
  params: [
    {
      name: 'title',
      location: 'body',
      required: true,
      schema: m.string()
    },
    {
      name: 'body',
      location: 'body',
      required: true,
      schema: m.string()
    }
  ],
  schema: PostSchema
})

Step 3: Use in Your App

import { getPosts, createPost } from './api/posts'

// Fetch posts
async function loadPosts() {
  const posts = await getPosts()
  console.log(posts) // Array of realistic post objects
}

// Create a post
async function addPost() {
  const newPost = await createPost({
    title: 'My First Post',
    body: 'This is the content...'
  })
  console.log(newPost) // Newly created post
}

That's it! You now have a working REST API with:

  • ✅ Realistic AI-generated data
  • ✅ Full TypeScript support
  • ✅ No server to run
  • ✅ Smart cloud caching for faster responses

Generation Modes

By default, Symulate uses AI to generate realistic, contextual data. However, you can also use:

Faker Mode - For CI/CD and unlimited free generation:

import { configureSymulate } from '@symulate/sdk'

configureSymulate({
  generateMode: 'faker' // Unlimited free mode using Faker.js
})

Auto Mode - Automatically falls back to Faker when AI tokens run out:

configureSymulate({
  generateMode: 'auto' // Uses AI first, falls back to Faker (default)
})

Per-Endpoint Override - Force mock mode for specific endpoints:

export const getPosts = defineEndpoint({
  path: '/api/posts',
  method: 'GET',
  schema: PostSchema,
  mock: {
    count: 10 // Generate 10 posts
  },
  mode: 'mock' // Force mock mode even in production
})

Note: The mode parameter forces either 'mock' or 'production' mode for a specific endpoint, overriding the global environment setting. The mock generation type (AI or Faker) is still controlled by the global generateMode in configureSymulate.


Method 2: JSON Server (Quickest Setup)

Best for: Simple prototypes and learning

Setup time: 1 minute

Step 1: Install JSON Server

npm install -g json-server

Step 2: Create Data File

Create db.json:

{
  "posts": [
    {
      "id": 1,
      "title": "Hello World",
      "body": "This is my first post",
      "author": "John Doe",
      "publishedAt": "2025-01-15"
    },
    {
      "id": 2,
      "title": "Learning REST APIs",
      "body": "REST APIs are awesome!",
      "author": "Jane Smith",
      "publishedAt": "2025-01-16"
    }
  ],
  "comments": [
    {
      "id": 1,
      "postId": 1,
      "text": "Great post!",
      "author": "Alice"
    }
  ]
}

Step 3: Start the Server

json-server --watch db.json --port 3000

Step 4: Use the API

You automatically get these endpoints:

# Get all posts
GET    http://localhost:3000/posts

# Get single post
GET    http://localhost:3000/posts/1

# Create post
POST   http://localhost:3000/posts

# Update post
PUT    http://localhost:3000/posts/1
PATCH  http://localhost:3000/posts/1

# Delete post
DELETE http://localhost:3000/posts/1

# Search/filter
GET    http://localhost:3000/posts?author=John%20Doe
GET    http://localhost:3000/posts?_sort=publishedAt&_order=desc

Bonus features:

  • Pagination: ?_page=1&_limit=10
  • Full-text search: ?q=hello
  • Relationships: /posts/1/comments
  • Custom routes via routes.json

Method 3: Mocky (No Installation)

Best for: Quick demos and sharing

Setup time: 30 seconds

Step 1: Go to Mocky.io

Visit https://designer.mocky.io

Step 2: Create Your Mock

  1. Set HTTP Method (GET, POST, etc.)
  2. Set Status Code (200, 404, etc.)
  3. Add Response Body:
{
  "posts": [
    {
      "id": "uuid-1",
      "title": "Hello World",
      "author": "John Doe"
    },
    {
      "id": "uuid-2",
      "title": "Mock APIs",
      "author": "Jane Smith"
    }
  ]
}
  1. Click Generate

Step 3: Use the URL

You get a URL like:

https://run.mocky.io/v3/abc123...

Use it in your code:

fetch('https://run.mocky.io/v3/abc123...')
  .then(res => res.json())
  .then(data => console.log(data))

Limitations:

  • Fixed responses (no dynamic data)
  • Manual setup for each endpoint
  • Requires internet connection

Full Example: Blog Application

Let's build a complete fake API for a blog:

Using Symulate (Full Example)

// api/blog.ts
import { defineEndpoint, m, type Infer } from '@symulate/sdk'

// Schemas
const AuthorSchema = m.object({
  id: m.uuid(),
  name: m.person.fullName(),
  avatar: m.internet.avatar(),
  bio: m.lorem.sentence()
})

const CommentSchema = m.object({
  id: m.uuid(),
  author: AuthorSchema,
  text: m.lorem.paragraph(),
  createdAt: m.date({ max: 'now' }),
  likes: m.number({ min: 0, max: 50 })
})

const PostSchema = m.object({
  id: m.uuid(),
  title: m.lorem.sentence(),
  slug: m.lorem.slug(),
  body: m.lorem.paragraphs({ min: 3, max: 6 }),
  excerpt: m.lorem.paragraph(),
  author: AuthorSchema,
  publishedAt: m.date({ max: 'now' }),
  updatedAt: m.date({ max: 'now' }),
  tags: m.array(m.string('blog post tag'), { min: 2, max: 5 }),
  likes: m.number({ min: 0, max: 500 }),
  views: m.number({ min: 100, max: 10000 }),
  commentsCount: m.number({ min: 0, max: 50 })
})

// Infer TypeScript types
type Author = Infer<typeof AuthorSchema>
type Comment = Infer<typeof CommentSchema>
type Post = Infer<typeof PostSchema>

// Response type for getPosts
const GetPostsResponseSchema = m.object({
  posts: m.array(PostSchema),
  total: m.number(),
  page: m.number(),
  limit: m.number()
})
type GetPostsResponse = Infer<typeof GetPostsResponseSchema>

// Endpoints
export const getPosts = defineEndpoint<GetPostsResponse>({
  path: '/api/posts',
  method: 'GET',
  params: [
    {
      name: 'page',
      location: 'query',
      required: false,
      schema: m.number()
    },
    {
      name: 'limit',
      location: 'query',
      required: false,
      schema: m.number()
    },
    {
      name: 'tag',
      location: 'query',
      required: false,
      schema: m.string()
    }
  ],
  schema: GetPostsResponseSchema,
  mock: {
    count: 1 // Returns single object containing posts array
  }
})

export const getPost = defineEndpoint<Post>({
  path: '/api/posts/:slug',
  method: 'GET',
  params: [
    {
      name: 'slug',
      location: 'path',
      required: true,
      schema: m.string()
    }
  ],
  schema: PostSchema
})

export const getComments = defineEndpoint<Comment[]>({
  path: '/api/posts/:postId/comments',
  method: 'GET',
  params: [
    {
      name: 'postId',
      location: 'path',
      required: true,
      schema: m.uuid()
    }
  ],
  schema: CommentSchema,
  mock: {
    count: 10 // Generate 10 comments
  }
})

export const createComment = defineEndpoint<Comment>({
  path: '/api/posts/:postId/comments',
  method: 'POST',
  params: [
    {
      name: 'postId',
      location: 'path',
      required: true,
      schema: m.uuid()
    },
    {
      name: 'text',
      location: 'body',
      required: true,
      schema: m.string()
    },
    {
      name: 'authorId',
      location: 'body',
      required: true,
      schema: m.uuid()
    }
  ],
  schema: CommentSchema
})

const LikeResponseSchema = m.object({
  likes: m.number()
})
type LikeResponse = Infer<typeof LikeResponseSchema>

export const likePost = defineEndpoint<LikeResponse>({
  path: '/api/posts/:postId/like',
  method: 'POST',
  params: [
    {
      name: 'postId',
      location: 'path',
      required: true,
      schema: m.uuid()
    }
  ],
  schema: LikeResponseSchema
})

Using in React

import { useEffect, useState } from 'react'
import { getPosts, getPost, getComments, type Post } from './api/blog'

function BlogList() {
  const [posts, setPosts] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    getPosts({ page: 1, limit: 10 })
      .then(data => setPosts(data.posts))
      .finally(() => setLoading(false))
  }, [])

  if (loading) return <div>Loading...</div>

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
          <div>
            <span>{post.author.name}</span>
            <span>{post.likes} likes</span>
            <span>{post.commentsCount} comments</span>
          </div>
        </article>
      ))}
    </div>
  )
}

function BlogPost({ slug }) {
  const [post, setPost] = useState(null)
  const [comments, setComments] = useState([])

  useEffect(() => {
    Promise.all([
      getPost({ slug }),
      getComments({ postId: slug })
    ]).then(([postData, commentsData]) => {
      setPost(postData)
      setComments(commentsData)
    })
  }, [slug])

  if (!post) return <div>Loading...</div>

  return (
    <article>
      <h1>{post.title}</h1>
      <div>
        <img src={post.author.avatar} alt={post.author.name} />
        <span>{post.author.name}</span>
        <time>{new Date(post.publishedAt).toLocaleDateString()}</time>
      </div>
      <div dangerouslySetInnerHTML={{ __html: post.body }} />

      <section>
        <h3>{comments.length} Comments</h3>
        {comments.map(comment => (
          <div key={comment.id}>
            <strong>{comment.author.name}</strong>
            <p>{comment.text}</p>
          </div>
        ))}
      </section>
    </article>
  )
}

Advanced Features

1. Error Simulation

Test how your app handles errors using the errors array:

export const getPosts = defineEndpoint({
  path: '/api/posts',
  method: 'GET',
  schema: PostSchema,
  mock: {
    count: 10
  },
  errors: [
    {
      code: 500,
      description: 'Internal server error',
      schema: m.object({
        error: m.object({
          message: m.string(),
          code: m.string()
        })
      }),
      failNow: true // Simulates this error in mock mode
    }
  ]
})

When failNow: true is set, the endpoint will throw this error in development/mock mode, allowing you to test error handling.

2. Loading Delays

Test loading states using the delay parameter in mock config:

export const getPosts = defineEndpoint({
  path: '/api/posts',
  method: 'GET',
  schema: PostSchema,
  mock: {
    count: 10,
    delay: 2000 // 2 second delay for cached responses
  }
})

The delay parameter simulates network latency for cached data, helping you test loading states and spinners.

3. Authentication

Mock auth flows:

export const login = defineEndpoint({
  path: '/auth/login',
  method: 'POST',
  params: [
    {
      name: 'email',
      location: 'body',
      required: true,
      schema: m.email()
    },
    {
      name: 'password',
      location: 'body',
      required: true,
      schema: m.string()
    }
  ],
  schema: m.object({
    token: m.string(),
    user: m.object({
      id: m.uuid(),
      email: m.email(),
      name: m.person.fullName()
    })
  })
})

4. Pagination

Implement proper pagination:

export const getPosts = defineEndpoint({
  path: '/api/posts',
  method: 'GET',
  params: [
    {
      name: 'page',
      location: 'query',
      required: false,
      schema: m.number(),
      example: 1
    },
    {
      name: 'limit',
      location: 'query',
      required: false,
      schema: m.number(),
      example: 10
    }
  ],
  schema: m.object({
    posts: m.array(PostSchema),
    pagination: m.object({
      page: m.number(),
      limit: m.number(),
      total: m.number(),
      pages: m.number()
    })
  }),
  mock: {
    count: 1 // Single response object containing posts array
  }
})

Comparison Table

FeatureSymulateJSON ServerMocky
Setup Time2 min1 min30 sec
Realistic Data✅ AI-powered❌ Manual❌ Manual
TypeScript✅ Full❌ No❌ No
Local/CloudBothLocal onlyCloud only
Free Tier20K AI tokens + unlimited FakerUnlimitedLimited
Learning CurveLowVery lowNone
Production Ready✅ Yes⚠️ Prototypes❌ No

Common Issues and Solutions

Issue 1: CORS Errors

Problem: Browser blocks requests

Solution:

  • JSON Server: json-server --watch db.json --port 3000 --host 0.0.0.0
  • Symulate: Runs locally, no CORS issues
  • Mocky: Add CORS headers in response settings

Issue 2: Data Persistence

Symulate's Smart Caching:

One of Symulate's main features is intelligent cloud-based caching. When data is generated for an endpoint:

  1. Cloud Storage: Generated data is stored in the cloud
  2. Automatic Reuse: Same endpoint with same schema reuses cached data
  3. Local Cache Option: Can also be stored in localStorage for faster responses
  4. Schema Change Detection: If endpoint schema or config changes, new data is automatically generated
  5. Configuration Control: Use regenerateOnConfigChange to control when cache invalidates
import { configureSymulate } from '@symulate/sdk'

configureSymulate({
  cacheEnabled: true,
  persistentCache: true, // Enable localStorage persistence in browser
  regenerateOnConfigChange: true // Regenerate when config changes (default)
})

Other Tools:

  • JSON Server: Data persists in db.json file
  • Mocky: No persistence (fixed responses)

Issue 3: Slow Requests

Problem: Requests take too long

Solution:

  • Add delays intentionally to test loading states
  • Remove delays for development
  • Use caching for repeated requests

When to Switch to Real API

Switch when:

  • ✅ Backend API is ready
  • ✅ Integration tests pass
  • ✅ Performance meets requirements
  • ✅ Error handling is tested

With Symulate, switching is one configuration change:

import { configureSymulate } from '@symulate/sdk'

// Development (uses mocks)
configureSymulate({
  environment: 'development',
  symulateApiKey: 'your-api-key',
  projectId: 'your-project-id'
})

// Production (uses real backend)
configureSymulate({
  environment: 'production',
  backendBaseUrl: 'https://api.yourapp.com'
})

Done! All your defineEndpoint calls automatically switch to the real backend. No code refactoring needed.


Next Steps

  1. Choose your method:
    • Quick prototype → JSON Server
    • Production app → Symulate
    • Simple demo → Mocky
  2. Build something:
    • Start with GET endpoints
    • Add POST/PUT/DELETE as needed
    • Test error states
  3. Learn more:

Conclusion

Creating a fake REST API doesn't have to be complicated. Whether you use Symulate (AI-powered), JSON Server (quick setup), or Mocky (no installation), you can have a working API in under 5 minutes.

Our recommendation: Start with Symulate if you're building a real application, or JSON Server if you're just learning.

Ready to start? Get Symulate with 20K free AI tokens (plus unlimited Faker mode), or install JSON Server and start building!


Resources:

Ready to try Symulate?

Start building frontends without waiting for backend APIs. Get 100K free AI tokens.

Sign Up Free