{"id":16417078,"url":"https://github.com/tkrotoff/stub-server","last_synced_at":"2026-04-22T21:37:57.294Z","repository":{"id":43805778,"uuid":"488344439","full_name":"tkrotoff/stub-server","owner":"tkrotoff","description":"Stub server for REST APIs","archived":false,"fork":false,"pushed_at":"2023-03-11T15:05:58.000Z","size":1137,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-15T18:59:35.355Z","etag":null,"topics":["fake","json","mock","server","stub","stub-server","test"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tkrotoff.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-05-03T19:53:06.000Z","updated_at":"2022-05-03T19:56:44.000Z","dependencies_parsed_at":"2023-01-24T02:30:14.217Z","dependency_job_id":null,"html_url":"https://github.com/tkrotoff/stub-server","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/tkrotoff/stub-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrotoff%2Fstub-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrotoff%2Fstub-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrotoff%2Fstub-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrotoff%2Fstub-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tkrotoff","download_url":"https://codeload.github.com/tkrotoff/stub-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkrotoff%2Fstub-server/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261487361,"owners_count":23166070,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fake","json","mock","server","stub","stub-server","test"],"created_at":"2024-10-11T07:10:57.108Z","updated_at":"2026-04-22T21:37:52.265Z","avatar_url":"https://github.com/tkrotoff.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @tkrotoff/stub-server\n\n[![npm version](https://badge.fury.io/js/%40tkrotoff%2Fstub-server.svg)](https://www.npmjs.com/package/@tkrotoff/stub-server)\n[![Node.js CI](https://github.com/tkrotoff/stub-server/workflows/Node.js%20CI/badge.svg?branch=master)](https://github.com/tkrotoff/stub-server/actions)\n[![Bundle size](https://badgen.net/bundlephobia/minzip/@tkrotoff/stub-server)](https://bundlephobia.com/result?p=@tkrotoff/stub-server)\n[![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)\n[![Airbnb Code Style](https://badgen.net/badge/code%20style/airbnb/ff5a5f?icon=airbnb)](https://github.com/airbnb/javascript)\n\nStub/mock server for REST APIs\n\nFor each route, decide what will happen: a JSON stub, a piece of JS or redirect to a real server\n\n- Use it with Express, [webpack-dev-server](https://github.com/webpack/webpack-dev-server), Next.js or the command line\n- Supports stubs written in JSON, JS, TypeScript, HTML, jpg...\n- Can redirect requests to another host thx to [node-http-proxy](https://github.com/http-party/node-http-proxy)\n- No need to restart stub-server if you modify a stub\n- The HTTP status code returned is determined from the stub filename: \\*\\_200\\_\\*.json, \\*\\_500\\_\\*.html...\n- Configurable delays to simulate slow APIs (helps find bugs and possible improvements to your app - add a spinner, disable a submit button...)\n- Access to request \u0026 response objects (allows to change headers, request URL...)\n\n## Usage\n\n`npm install --save-dev @tkrotoff/stub-server`\n\nExample: https://github.com/tkrotoff/MarvelHeroes\n\n### Proposed file organization\n\n```\nstubs/routes/my_api1_GET_200_OK.json\n             my_api2_GET_200_OK.jpg\n             my_api3_POST_400_BadRequest-invalidField.ts\n             my_api4_POST_400_BadRequest-invalidField.js\n             my_api5_DELETE_500_InternalServerError.html\n             my_api7_GET_200_OK.json\n             my_api7_POST_200_OK.json\n             my_api8_GET.ts\n             my_api9_GET.js\n             my_api10_GET_200_OK-param1.json\n             my_api10_GET_200_OK-param2.json\nstubs/config.ts\nwebpack.config.ts\n```\n\n### stubs/config.ts\n\n```TypeScript\nimport path from 'path';\nimport { StubServerConfig } from '@tkrotoff/stub-server';\n\nconst prod = 'https://myapp.com';\n\nconst stubsPath = path.resolve(__dirname, 'routes');\n\nconst config: StubServerConfig = {\n  delay: { min: 500, max: 3000 },\n  routes: {\n    '/my/api1': { GET: `${stubsPath}/my_api1_GET_200_OK.json` },\n    '/my/api2': { GET: `${stubsPath}/my_api2_GET_200_OK.jpg` },\n    '/my/api3': { POST: `${stubsPath}/my_api3_POST_400_BadRequest-invalidField.ts` },\n    '/my/api4': { POST: `${stubsPath}/my_api4_POST_400_BadRequest-invalidField.js` },\n    '/my/api5': { DELETE: `${stubsPath}/my_api5_DELETE_500_InternalServerError.html` },\n    '/my/api6/:id': {\n      // Here stub-server works as a proxy,\n      // example of request sent: PUT https://myapp.com/my/api6/132379\n      PUT: prod\n    },\n    '/my/api7': {\n      delay: { min: 1000, max: 1000 },\n      GET: `${stubsPath}/my_api7_GET_200_OK.json`,\n      POST: {\n        delay: { min: 0, max: 0 },\n        response: req =\u003e {\n          req.headers.origin = prod;\n          return `${stubsPath}/my_api7_POST_200_OK.json`;\n        }\n      }\n    },\n    '/my/api8': { GET: `${stubsPath}/my_api8_GET.ts`},\n    '/my/api9': { GET: `${stubsPath}/my_api9_GET.js`},\n    '/my/api10/:id': { GET: req =\u003e `${stubsPath}/my_api10_GET_200_OK-${req.params.id}.json` }\n  }\n};\n\nconst rootApiPath = 'https://myapp.com/client/:clientApi';\nconfig.routes[`${rootApiPath}/my/api7`] = { GET: `${stubsPath}/my_api7_GET_200_OK.json` };\n\nexport default config; // Or \"exports.default = config\"\n```\n\nNote: `stubs/config.ts` written in TypeScript instead of JavaScript requires [ts-node](https://github.com/TypeStrong/ts-node)\n\n### webpack.config.ts\n\nConfiguration with [webpack-dev-server](https://github.com/webpack/webpack-dev-server)\n\n```TypeScript\nimport { stubServer } from '@tkrotoff/stub-server';\n\n...\n\n  devServer: {\n    // With webpack-dev-server \u003e= v4.7.0\n    setupMiddlewares: (middlewares, devServer) =\u003e {\n      const configPath = path.resolve(__dirname, 'stubs/config');\n      stubServer(configPath, devServer.app!);\n      return middlewares;\n    }\n\n    // With webpack-dev-server from v4.0.0 to v4.6.0\n    onBeforeSetupMiddleware: (devServer) =\u003e {\n      const configPath = path.resolve(__dirname, 'stubs/config');\n      stubServer(configPath, devServer.app!);\n    }\n\n    // With webpack-dev-server \u003c v4.0.0\n    before: app =\u003e {\n      const configPath = path.resolve(__dirname, 'stubs/config');\n      stubServer(configPath, app);\n    }\n\n    ...\n  },\n```\n\n### stubs/routes/my_api1_GET_200_OK.json\n\n```JSON\n{\n  \"foo\": \"bar\"\n}\n```\n\n### stubs/routes/my_api3_POST_400_BadRequest-invalidField.ts\n\n```TypeScript\nexport default {\n  error: 'Invalid field'\n};\n```\n\n### stubs/routes/my_api4_POST_400_BadRequest-invalidField.js\n\n```JavaScript\nmodule.exports = {\n  error: 'Invalid field'\n};\n```\n\n### stubs/routes/my_api8_GET.ts\n\n```TypeScript\nimport express from 'express';\n\nexport default function stub(req: express.Request, res: express.Response) {\n  res.send('Hello, World!');\n}\n```\n\n### stubs/routes/my_api9_GET.js\n\n```JavaScript\nmodule.exports = (req, res) =\u003e {\n  res.send('Hello, World!');\n};\n```\n\n## Command line\n\n```\nUsage: stub-server [options] \u003cconfig\u003e\n\nArguments:\n  config         path to the config file\n\nOptions:\n  --port \u003cport\u003e  stub server port (default: \"12345\")\n  --no-delay     ignore any delay specified in the config\n  -h, --help     display help for command\n```\n\n## Next.js\n\n```JavaScript\n// stubServer.js\n\nconst { stubServer } = require('@tkrotoff/stub-server');\nconst cors = require('cors');\nconst express = require('express');\nconst next = require('next');\nconst Log = require('next/dist/build/output/log');\nconst path = require('path');\n\nconst app = next({ dev: process.env.NODE_ENV !== 'production' });\nconst nextjsHandler = app.getRequestHandler();\n\nconst configPath = path.resolve(__dirname, 'stubs/config');\nconst port = 3000;\n\napp.prepare().then(() =\u003e {\n  const server = express();\n  server.use(express.json());\n  server.use(cors());\n\n  stubServer(configPath, server);\n\n  // Next.js only processes GET requests unless you are using [API Routes](https://nextjs.org/docs/api-routes/introduction)\n  server.get('*', (req, res) =\u003e nextjsHandler(req, res));\n\n  server.all('*', req =\u003e {\n    throw new Error(`'${req.method} ${req.url}' should be declared in stubs/config.ts`);\n  });\n\n  server.listen(port, () =\u003e {\n    Log.ready(`ready on port ${port}`);\n  });\n});\n```\n\n```JavaScript\n// package.json\n\n  \"scripts\": {\n    \"dev\": \"node stubServer.js\", // Instead of \"next dev\"\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n    ...\n  },\n```\n\n## How to configure stub-server both for stubs and as a proxy\n\nTo \"connect\" your app to an [environment](https://en.wikipedia.org/wiki/Deployment_environment) (prod, staging, integration...), you can use stub-server as a proxy (intermediary between your app requesting a resource and the server providing that resource).\n\nWhy? To configure delays for the HTTP requests (helps find bugs and possible improvements to your app), access to request \u0026 response objects...\n\n```JavaScript\n// package.json\n\n  \"scripts\": {\n    \"start\": \"...\",\n    \"start:staging\": \"ENVIRONMENT_NAME=staging npm run start\",\n    \"start:prod\": \"ENVIRONMENT_NAME=prod npm run start\"\n    ...\n  },\n```\n\n```TypeScript\n// stubs/config.ts\n\nimport path from 'path';\nimport express from 'express';\nimport { GetStubFilenameOrUrl, StubServerConfig } from '@tkrotoff/stub-server';\n\nconst stubsPath = path.resolve(__dirname, 'routes');\n\ntype Environments = { [name: string]: { target: string; origin: string } | undefined };\n\nconst environments: Environments = {\n  staging: {\n    target: 'https://api.staging.myapp.com',\n    origin: 'https://staging.myapp.com'\n  },\n  prod: {\n    target: 'https://api.myapp.com',\n    origin: 'https://myapp.com'\n  }\n};\n\nconst { target, origin } =\n  environments[process.env.ENVIRONMENT_NAME ?? 'No environment specified'] ?? {};\n\nfunction configureRequest(stubOrUrl: string): GetStubFilenameOrUrl {\n  return (req: express.Request) =\u003e {\n    // You can rewrite the request URL if needed\n    req.url = req.url.replace('/prefix', '');\n\n    if (origin !== undefined) {\n      // May be required for some APIs\n      req.headers.origin = origin;\n    }\n\n    return stubOrUrl;\n  };\n}\n\nconst config: StubServerConfig = {\n  delay: { min: 500, max: 3000 },\n  routes: {\n    '/prefix/my/api1': {\n      GET: configureRequest(\n        // Here stub-server works as a proxy,\n        // example of request sent: GET https://api.staging.myapp.com/my/api1\n        target ??\n        // ...or use a stub if no target specified\n        `${stubsPath}/my_api1_GET_200_OK.json`\n      )\n    },\n    ...\n  }\n};\n\nexport default config;\n```\n\n## Output generated\n\nExamples of output (stdout) for requests processed by stub-server:\n\n```\nGET /account/payment-types?imagesWithUrl=true =\u003e stubs/routes/payment-types_200_OK.ts, delay: 442 ms\nGET /account/captcha/1662973203224 =\u003e https://api.myapp.com, delay: 0 ms\nGET /account/captcha/1663061235576 =\u003e stubs/routes/captcha_200_OK-242674.jpg, delay: 4 ms\nPOST /account/create =\u003e stubs/routes/account_create_201_Created-no-appro.json, delay: 169 ms\nPOST /auth/session?withNotification=true =\u003e https://api.myapp.com, delay: 34 ms\n```\n\n## Errors\n\nIf the stub is missing or the target is unknown, stub-server returns a 500 (Internal Server Error) response with the error in HTML format and also displays the error in the console.\n\nExamples of errors in the console:\n\n- `ENOENT: no such file or directory, open 'stubs/routes/captcha_200_OK-242674.jpg'`\n- `Error: getaddrinfo ENOTFOUND api.myapp.com`\n\nIf you request an unknown route, stub-server does not process it and therefore won't display anything in the console. It's up to you to handle this case with a catch-all route:\n\n```TypeScript\nfunction missingStubHandler(req: express.Request) {\n  throw new Error(`Missing stub for '${req.method} ${req.url}'`);\n}\n\n...\n\n'/prefix/*': {\n  GET: missingStubHandler,\n  POST: missingStubHandler,\n  PUT: missingStubHandler,\n  PATCH: missingStubHandler,\n  DELETE: missingStubHandler\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkrotoff%2Fstub-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftkrotoff%2Fstub-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkrotoff%2Fstub-server/lists"}