Build server | Platform | Build status |
---|---|---|
Travis | Linux / macOS | |
AppVeyor | Windows |
Fake JSON Server is a Fake REST API that can be used as a Back End for prototyping or as a template for a CRUD Back End. Fake JSON Server also has an an experimental GraphQL query and mutation support.
- No need to define types for resources, uses dynamic typing
- No need to define routes, routes are handled dynamically
- No database, data is stored to a single JSON file
- No setup required, just start the server and API is ready to be used with any data
- API is built following the best practices and can be used as a reference when building your own API
- Can be run on Windows, Linux and macOS without any installation or prerequisites from executable or with Docker
- See features listed below
- Supported HTTP methods #
- All methods for CRUD operations (GET, PUT, POST, PATCH, DELETE)
- Methods for fetching resource information (HEAD, OPTIONS)
- Async versions of update operations with long running operations and queues #
- REST API follows best practices from multiple guides
- Uses correct Status Codes, Headers, etc.
- As all guides have slightly different recommendations, this compilation is based on our opinions
- Token and Basic Authentication #
- WebSocket update notifications #
- Simulate delay and errors for requests #
- Static files #
- Swagger #
- CORS #
- Caching and avoiding mid-air collisions with ETag #
- Experimental GraphQL query and mutation support #
- ASP.NET Core 2.0 / C# 7
- Uses JSON Flat File Data Store to store data
- Can be used without .NET
Click to here to see contents
# Get source code from GitHub
$ git clone https://github.com/ttu/dotnet-fake-json-server.git
$ cd dotnet-fake-json-server/FakeServer
$ dotnet run [--file] [--urls]
# Optional arguments:
# --file Data store's JSON file (default datastore.json)
# --urls Server url (default http://localhost:57602)
# Example: Start server
$ dotnet run --file data.json --urls http://localhost:57602
If you don't have .NET installed, you can run the server with Docker.
# Get source code from GitHub
$ git clone https://github.com/ttu/dotnet-fake-json-server.git
$ cd dotnet-fake-json-server
$ docker build -t fakeapi .
# Run in foreground
$ docker run -it -p 57602:57602 fakeapi
# Run in detached mode (run in background)
$ docker run -it -d -p 57602:57602 fakeapi
Copy JSON-file to container. Filename is db.json
# Check container id (image name is fakeapi)
$ docker ps
# Copy file from host to container
$ docker cp db.json [ContainerId]:/app/db.json
# Copy file from container to host
$ docker cp [ContainerId]:/app/db.json db.json
docker run
will reset JSON-file, so copy it before closing the server.
The self-contained application archive contains Fake JSON Server, .NET Core runtime and all required third-party dependencies. No installation or prerequisites are needed.
- Go to Latest Release
- Download correct archive matching your OS
- Extract files and execute
E.g. download and execute version 0.6.0 for macOS
$ mkdir FakeServer && cd FakeServer
$ wget https://github.com/ttu/dotnet-fake-json-server/releases/download/0.6.0/fakeserver-osx-x64.tar.gz
$ tar -zxvf fakeserver-osx-x64.tar.gz
$ chmod +x FakeServer
$ ./FakeServer
# List collections (should be empty, if data.json didn't exist before)
$ curl http://localhost:57602/api
# Insert new user
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name": "Phil", "age": 20, "location": "NY" }' http://localhost:57602/api/users/
# Insert another user
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name": "James", "age": 40, "location": "SF" }' http://localhost:57602/api/users/
# List users
$ curl http://localhost:57602/api/users
# List users from NY
$ curl http://localhost:57602/api/users?location=NY
# Get User with Id 1
$ curl http://localhost:57602/api/users/1
...
# Add users to data.json manually
# Get all users
$ curl http://localhost:57602/api/users/
...
# Or open url http://localhost:57602/swagger/ with browser and use Swagger
Redux TodoMVC example modified to use Fake JSON Server as a Back End.
Fake REST API supports Token and Basic Authentication.
Authentication can be disabled from authentication.json
by setting Enabled to false
. AuthenticationType
options are token
and basic
.
Add allowed usernames/passwords to Users
-array.
"Authentication": {
"Enabled": true,
"AuthenticationType": "token",
"Users": [
{ "Username": "admin", "Password": "root" }
]
}
API has a token provider middleware which provides an endpoint for token generation /token
.
Get token:
$ curl -X POST -H 'content-type: multipart/form-data' -F username=admin -F password=root http://localhost:57602/token
Add token to Authorization header:
$ curl -H 'Authorization: Bearer [TOKEN]' http://localhost:57602/api
Token authentication has also a logout functionality. By design tokens do not support token invalidation, so logout is implemented by blacklisting tokens.
$ curl -X POST -d '' -H 'Authorization: Bearer [TOKEN]' http://localhost:57602/logout
The implementation is quite similiar to SimpleTokenProvider and more info on that can be found from GitHub and StormPath's blog post.
NOTE: It is not recommended to use Basic Authentication in production as base64 is a reversible encoding
Add base64 encoded username:password to authorization header e.g. 'Authorization: Basic YWRtaW46cm9vdA=='
.
$ curl -u admin:root http://localhost:57602/api
# -u argument creates Authorization header with encoded username and password
$ curl -H 'Authorization: Basic YWRtaW46cm9vdA==' http://localhost:57602/api
API will send the latest update's method (POST, PUT, PATCH, DELETE
), path, collection and optional item id with WebSocket.
{ "method": "PATCH", "path": "/api/users/2", "collection": "users", "itemId": 2 }
wwwroot\index.html has a WebSocket example.
CORS is enabled and it allows everything.
GET /
Returns static files from wwwroot. Default file is index.html
.
Swagger is configured to endpoint /swagger
and Swagger UI opens when project is started from IDE.
Caching can be disabled from appsettings.json
by setting ETag.Enabled to false
.
"Caching": {
"ETag": {
"Enabled": true
}
}
If caching is enabled, ETag is added to response headers.
$ curl -v 'http://localhost:57602/api/users?age=40'
200 OK
Headers:
ETag: "5yZCXmjhk5ozJyTK4-OJkkd_X18"
If a request contains the If-None-Match
header, the header's value is compared to the response's body and if the value matches to the body's checksum then 304 Not Modified
is returned.
$ curl -H "If-None-Match: \"5yZCXmjhk5ozJyTK4-OJkkd_X18\"" 'http://localhost:57602/api/users?age=40'
304 Not Modified
If the PUT
request contains the If-Match
header, the header's value is compared to the item to be updated. If the value matches to the item's checksum then items is updated, else 412 Precondition Failed
is returned.
GET /
POST /token
POST /logout
POST /admin/reload
GET /api
HEAD /api
GET /api/{collection}
HEAD /api/{collection}
POST /api/{collection}
GET /api/{collection}/{id}
HEAD /api/{collection}/{id}
PUT /api/{collection}/{id}
PATCH /api/{collection}/{id}
DELETE /api/{collection}/{id}
OPTIONS /api/*
GET /async/queue/{id}
DELETE /async/queue/{id}
POST /async/{collection}
PUT /async/{collection}/{id}
PATCH /async/{collection}/{id}
DELETE /async/{collection}/{id}
OPTIONS /async/*
POST /graphql
Dynamic routes are defined by the name of item's collection and id: api/{collection}/{id}
. All examples below use users
as a collection name.
If /api
or /async
are needed to change to something different, change ApiRoute
or AsyncRoute
from Config.cs
.
public class Config
{
public const string ApiRoute = "api";
public const string AsyncRoute = "async";
public const string GraphQLRoute = "graphql";
public const string TokenRoute = "token";
public const string TokenLogoutRoute = "logout";
}
For example, if api
-prefix is not wanted in the route, then remove api
from ApiRoute
.
public const string ApiRoute = "";
# Query with default route
$ curl 'http://localhost:57602/api/users?skip=5&take=20'
# Query with updated route
$ curl 'http://localhost:57602/users?skip=5&take=20'
id
is used as the identifier field. By default Id field's type is integer. POST
will always use integer as id field's type.
"users":[
{ "id": 1 }
],
"sensors": [
{ "id": "E:52:F7:B3:65:CC" }
]
If string is used as the identifiers type, then items must be inserted with PUT
and UpsertOnPut
must be set to true from appsettings.json
.
Asynchoronous operations follow the REST CookBook guide. Updates will return 202
with location header to queue item. Queue will return 200
while operation is processing and 303
when job is ready with location header to changed or new item.
Method return codes are specified in REST API Tutorial.
OPTIONS method will return Allow
header with a list of HTTP methods that may be used on the resource.
$ curl -X OPTIONS -v http://localhost:57602/api/
200 OK
Headers:
Allow: GET, POST, OPTIONS
HEAD method can be used to get the metadata and headers without receiving response body.
E.g. get user count without downloading large response body.
$ curl -X HEAD -v http://localhost:57602/api/users
200 OK
Headers:
X-Total-Count: 1249
By default Data Store updates its internal data on every request by reading the data from the JSON file.
EagerDataReload
can be configured from appsettings.json
.
"Common": {
"EagerDataReload": true
}
For performance reasons EagerDataReload
can be changed to false. Then the data is reloaded from the file only when Data Store is initialized and when the data is updated.
If EagerDataReload
is false and JSON file is updated manually, reload endpoint must be called if new data will be queried before any updates.
Reload endpoint can be used to reload JSON data from the file to Data Store. Endpoint is in Admin controller, so it is usable also with Swagger.
$ curl -X POST http://localhost:57602/admin/reload --data ""
Data used in example requests, unless otherwise stated:
{
"users": [
{ "id": 1, "name": "Phil", "age": 40, "location": "NY" },
{ "id": 2, "name": "Larry", "age": 37, "location": "London" },
{ "id": 3, "name": "Thomas", "age": 40, "location": "London" }
],
"movies": []
}
Example JSON generation guide for data used in unit tests CreateJSON.md.
> GET /api
200 OK : List of collections
Get all collections.
$ curl http://localhost:57602/api
[ "users", "movies" ]
> GET /api/{collection}
200 OK : Collection is found
400 Bad Request : Invalid query parameters
404 Not Found : Collection is not found or it is empty
By default the request returns results in an array. Headers have the collection's total item count (X-Total-Count
) and pagination links (Link
).
$ curl http://localhost:57602/api/users
[
{ "id": 1, "name": "Phil", "age": 40, "location": "NY" },
{ "id": 2, "name": "Larry", "age": 37, "location": "London" },
{ "id": 3, "name": "Thomas", "age": 40, "location": "London" },
...
]
Headers:
X-Total-Count=20
Link=
<http://localhost:57602/api/users?offset=15&limit=5>; rel="next",
<http://localhost:57602/api/users?offset=15&limit=5>; rel="last",
<http://localhost:57602/api/users?offset=0&limit=5>; rel="first",
<http://localhost:57602/api/users?offset=5&limit=5>; rel="prev"
The return value can also be a JSON object. Set UseResultObject
to true from appsettings.json
.
"Api": {
"UseResultObject": true
}
JSON object has items in results array in result field, link object has the pagination info, skip, take and total count fields.
{
"results": [
...
],
"link": {
"Prev": "http://localhost:57602/api/users?offset=5&limit=5",
"Next": "http://localhost:57602/api/users?offset=15&limit=5",
"First": "http://localhost:57602/api/users?offset=0&limit=5",
"Last": "http://localhost:57602/api/users?offset=15&limit=5"
},
"offset": 10,
"limit": 5,
"count": 20
}
Slicing can be defined with skip
/take
or with offset
/limit
parameters. By default request returns the first 512 items.
Example request returns items from 6 to 26.
# skip and take
$ curl 'http://localhost:57602/api/users?skip=5&take=20'
# offset and limit
$ curl 'http://localhost:57602/api/users?offset=5&limit=20'
Link items are optional, so e.g. if requested items are starting from index 0, then the prev and first page link won't be added to the Link header.
Headers follow GitHub Developer guide.
> GET api/{collection}?field=value&otherField=value
Get all users whose age
equals to 40.
$ curl 'http://localhost:57602/api/users?age=40'
[
{ "id": 1, "name": "Phil", "age": 40, "location": "NY" },
{ "id": 3, "name": "Thomas", "age": 40, "location": "London" }
]
Query can have a path to child properties. Property names are separated by periods.
> GET api/{collection}?parent.child.grandchild.field=value
Example JSON:
[
{
"companyName": "ACME",
"employees": [
{ "id": 1, "name": "Thomas", "address": { "city": "London" } }
]
},
{
"companyName": "Box Company",
"employees": [
{ "id": 1, "name": "Phil", "address": { "city": "NY" } }
]
}
]
Get all companies which has employees with London in address.city
.
$ curl http://localhost:57602/api/companies?employees.address.city=London
Query will return ACME from the example JSON.
[
{
"companyName": "ACME",
"employees": [
{ "id": 1, "name": "Thomas", "address": { "city": "London" } }
]
}
]
Query filter can include operators. Operator identifier is added to the end of the field.
> GET api/{collection}?field{operator}=value
= : Equal to
_ne= : Not equal
_lt= : Less than
_gt= : Greater than
_lte= : Less than or equal to
_gte= : Greater than or equal to
Query users with age
less than 40.
$ curl http://localhost:57602/api/users?age_lt=40
[
{ "id": 2, "name": "Larry", "age": 37, "location": "London" }
]
Full-text search can be performed with the q
-parameter followed by search text. Search is not case sensitive.
> GET api/{collection}?q={text}
Get all users that contain text London in the value of any of it's properties.
$ curl http://localhost:57602/api/users?q=london
Choose which fields to include in the results. Field names are separated by comma.
> GET api/{collection}?fields={fields}
Select age
and name
from users.
$ curl http://localhost:57602/api/users?fields=age,name
[
{ "name": "Phil", "age": 40 },
{ "name": "Larry", "age": 37 }
]
> GET /api/{collection}/{id}
200 OK : Item is found
400 Bad Request : Collection is not found
404 Not Found : Item is not found
Get user with id
1.
$ curl http://localhost:57602/api/users/1
{ "id": 1, "name": "Phil", "age": 40, "location": "NY" }
> GET /api/{collection}/{id}/{restOfThePath}
200 OK : Nested item is found
400 Bad Request : Parent item is not found
404 Not Found : Nested item is not found
It is possible to request only child objects instead of full item. Path to nested item can contain id field integers and property names.
[
{
"id": 0,
"companyName": "ACME",
"employees": [
{ "id": 1, "name": "Thomas", "address": { "city": "London" } }
]
}
]
Example query will return address object from the employee with id
1 from the company with id
0.
$ curl http://localhost:57602/api/company/0/employees/1/address
{ "address": { "city": "London" } }
> POST /api/{collection}
201 Created : New item is created
400 Bad Request : New item is null
Add { "name": "Phil", "age": 40, "location": "NY" } to users.
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name": "Phil", "age": 40, "location": "NY" }' http://localhost:57602/api/users/
Response has new item's id and a Location header that contains the path to the new item.
{ "id": 6 }
Headers:
Location=http://localhost:57602/api/users/6
> PUT /api/{collection}/{id}
204 No Content : Item is replaced
400 Bad Request : Item is null
404 Not Found : Item is not found
Replace user with id
1 with object { "name": "Roger", "age": 28, "location": "SF" }.
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X PUT -d '{ "name": "Roger", "age": 28, "location": "SF" }' http://localhost:57602/api/users/1
> PATCH /api/{collection}/{id}
204 No Content : Item updated
400 Bad Request : PATCH is empty
404 Not Found : Item is not found
Set name
to Timmy from user with id
1.
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X PATCH -d '{ "name": "Timmy" }' http://localhost:57602/api/users/1
> DELETE /api/{collection}/{id}
204 No Content : Item deleted
404 Not Found : Item is not found
Delete user with id 1.
$ curl -X DELETE http://localhost:57602/api/users/1
/async
endpoint has long running operation for each update operation.
> POST/PUT/PATCH/DELETE /async/{collection}/{id}
202 Accepted : New job started
400 Bad Request : Job not started
Headers:
Location=http://{url}:{port}/async/queue/{id}
Update operations will return location to job queue in headers.
Create new item. Curl has a verbose flag (-v
). When it is used, curl will print response headers among other information.
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name": "Phil", "age": 40, "location": "NY" }' -v http://localhost:57602/async/users/
> GET /async/queue/{id}
200 OK : Job running
303 See Other : Job ready
404 Not Found : Job not found
Headers:
Location=http://{url}:{port}/api/{collectionId}/{id}
When Job is ready, status code will be redirect See Other. Location header will have modified item's url.
After job is finished, it must be deleted manually
> DELETE /async/queue/{id}
204 No Content : Job deleted
404 Not Found : Job not found
Delay for operations can be set from appsettings.json
. With long delay it is easier to simulate long running jobs.
"Jobs": {
"DelayMs": 2000
}
Delay value is milliseconds. Default value is 2000ms.
GraphQL implementation is experimental and supports only basic queries and mutations. At the moment this is a good way to compare simple GraphQL and REST queries.
> POST /graphql
Content-type: application/graphql
Body: [query/mutation]
200 OK : Query/mutation successful
400 Bad Request : Query/mutation contains errors
501 Not Implemented : HTTP method and/or content-type combination not implemented
Response is in JSON format. It contains data
and errors
fields. errors
field is not present if there are no errors.
{
"data": {
"users": [ ... ],
...
},
"errors": [ ... ]
}
Implementation uses graphql-dotnet to parse Abstract Syntax Tree from the query.
Query implementation supports equal filtering with arguments. Query's first field is the name of the collection.
query {
[collection](filter: value) {
[field1]
[field2](filter: value) {
[field2.1]
}
[field3]
}
}
Implementation accepts queries with operation type, with any query name (which is ignored) and query shorthands.
# Operation type
query {
users(id: 3) {
name
work {
location
}
}
}
# Optional query name
query getUsers {
users {
name
work {
location
}
}
}
# Query shorthand
{
families {
familyName
children(age: 5){
name
}
}
}
Example: get familyName
and age
of the children
from families
where id
is 1 and name
from all users
.
{
families(id: 1) {
familyName
children {
age
}
}
users {
name
}
}
Execute query with curl:
$ curl -H "Content-type: application/graphql" -X POST -d '{ families(id: 1) { familyName children { age } } users { name } }' http://localhost:57602/graphql
Respose:
{
"data": {
"families": [
{
"familyName": "Day",
"children": [
{ "age": 14 },
{ "age": 18 },
{ "age": 9 }
]
}
],
"users": [
{ "name": "James" },
{ "name": "Phil" },
{ "name": "Raymond" },
{ "name": "Jarvis" }
]
}
}
Fake JSON Server supports dynamic mutations with the format defined below:
mutation {
[mutationName](input: {
[optional id]
[itemData/patch]
}) {
[collection]{
[fields]
}
}
Action is decided from the mutation name. Name follows pattern add|update|replace|delete[collection] E.g. deleteUsers will delete an item from the users collection. Input object has an optional id-field and update data object. Return data is defined the same way as in queries.
add{collection}
Input contains object to be added with the collection's name.
mutation {
addUsers(input: {
users: {
name: James
work: {
name: ACME
}
}
}) {
users {
id
name
}
}
}
Execute mutation with curl:
$ curl -H "Content-type: application/graphql" -X POST -d 'mutation { addUsers(input: { users: { name: James work: { name: ACME } } }) { users { id name } } }' http://localhost:57602/graphql
Response:
{
"data": {
"users":{
"id": 12,
"name": "James"
}
}
}
update{collection}
mutation {
updateUsers(input: {
id: 2
patch:{
name: Timothy
}
}) {
users {
id
name
}
}
}
Execute mutation with curl:
$ curl -H "Content-type: application/graphql" -X POST -d 'mutation { updateUsers(input: { id: 2 patch:{ name: Timothy } }) { users { id name age }}}' http://localhost:57602/graphql
Response:
{
"data": {
"users": {
"id": 2,
"name": "Timothy",
"age": 25
}
}
}
NOTE: Update doesn't support updating child arrays
replace{collection}
Input must contain id of the item to be replacecd and items full data in object named with collection's name.
mutation {
replaceUsers(input: {
id: 5
users:{
name: Rick
age: 44
workplace: {
companyName: ACME
}
}
}) {
users {
id
name
age
}
}
}
Execute mutation with curl:
$ curl -H "Content-type: application/graphql" -X POST -d 'mutation { replaceUsers(input: { id: 1 users: { name: Rick age: 44 workplace: { name: ACME } } }) {users {id name age}}}' http://localhost:57602/graphql
Response:
{
"data": {
"users": {"
id": 1,
"name": "Rick",
"age": 44
}
}
}
delete{collection}
Delete requires only the id of item to be deleted. Response will only contain success boolean true/false
, so mutation doesn't need any definition for return data.
mutation {
deleteUsers(input: {
id: 4
})
}
Execute mutation with curl:
$ curl -H "Content-type: application/graphql" -X POST -d 'mutation { deleteUsers(input: { id: 4 }) }' http://localhost:57602/graphql
Response:
{
"data": {
"Result": true
}
}
Delay and errors can be configured from appsettings.json
.
Delay can be simulated by setting Simulate.Delay.Enabled
to true. The inbound request is delayed. The length of the delay is randomly chosen between MinMs
and MaxMs
. Delay can be configured for only certain HTTP Methods, e.g. only POST updates have delay and all GET requests are handled normally.
"Simulate": {
"Delay": {
"Enabled": true,
"Methods": [ "GET", "POST", "PUT", "PATCH", "DELETE" ],
"MinMs": 2000,
"MaxMs": 5000
}
}
Random errors can be simulated by setting Simulate.Error.Enabled
to true. Error is thrown if set Probability
is greater or equal to randomly chosen value between 1 and 100. Error can be configured for only certain HTTP Methods.
"Simulate": {
"Error": {
"Enabled": true,
"Methods": [ "POST", "PUT", "PATCH", "DELETE" ],
"Probability": 50
}
}
Error simulation is always skipped for Swagger, WebSocket (ws) and for any html file.
API follows best practices and recommendations from these guides:
- REST CookBook
- REST API Tutorial
- Zalando Restful API Guidelines
- Microsoft API Design
- GitHub v3 Guide
- Introduction to GraphQL
- MDN Web Docs: ETag
- Designing GraphQL Mutations
Releases are marked with Tag and can be found from Releases.
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Licensed under the MIT License.