typicode / json-server

Get a full fake REST API with zero coding in less than 30 seconds (seriously)
Other
72.59k stars 7k forks source link

custom resource id #279

Open mvestil opened 8 years ago

mvestil commented 8 years ago

Currently, an option to set a custom id is provided using commandline option --id id

Is it possible to make id set to a format something like this :

--id resource_id where resource is the name of the resource...

So when you run json-server db.json --id resource_id, the below json db is valid

{
  "posts": [
    { "post_id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "comment_id": 1, "body": "some comment", "post_id": 1 }
  ],
  "profile_id": { "name": "typicode" }
}
mertkahyaoglu commented 8 years ago

We could also create a ids.json file and specify the each id of the resources like this

{
  "posts": "post_id",
  "comments": "comment_id"
}

I think this would allows us to create more generic ids, not just ones that follow some pattern.

JulienBrks commented 8 years ago

how to use ids.json @mertkahyaoglu

mertkahyaoglu commented 8 years ago

json-server db.json --id ids.json could work, like --routes routes.json

if we want all of the resource ids to be same, we could just create a json file like this;

{
  "id": "custom_id"
}

@JulienBrks

JulienBrks commented 8 years ago

I tried but not work. And I can't find doc about this way to use --id. May you give a demo or a detail doc link. @mertkahyaoglu

mertkahyaoglu commented 8 years ago

@JulienBrks Oh, it was just a suggestion. json-server does not have this feature, but it would be great to have. Sorry about the confusion.

hadynz commented 8 years ago

👍 for an ids.json

odranoelBR commented 8 years ago

:+1: for an ids.json

inNetto commented 8 years ago

:+1: for an ids.json

barrozorcb commented 8 years ago

:+1: for an ids.json

mouradsm commented 8 years ago

:+1: for an ids.json

helloheesu commented 7 years ago

(👍 for ids.json,) Is it being worked? or even not considered? 😢

inNetto commented 7 years ago

did u improved it? it could be awesome

2017-01-12 5:14 GMT-02:00 Heesu Jung notifications@github.com:

(👍 for ids.json,) Is it being worked? or even not considered? 😢

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/typicode/json-server/issues/279#issuecomment-272093320, or mute the thread https://github.com/notifications/unsubscribe-auth/AGN88SK_CScf60t-2fHq7ZYeXauSoNvwks5rRdLkgaJpZM4IZ3ub .

shyDn commented 7 years ago

for an ids.json, it's so great and helpful for us 👍

alexandermikuta commented 7 years ago

This would be really helpful.

markudevelop commented 7 years ago

+1

pavel06081991 commented 7 years ago

+1

FranciscoGregorio commented 7 years ago

+1

Ginxo commented 7 years ago

+1

kitayu commented 7 years ago

+1

StefanDuPreez commented 7 years ago

+1

typicode commented 7 years ago

Thank you for all the feedback 👍

JSON Server now supports a --foreignKeySuffix option, which lets you define how the foreign key should end (making it easier to fake Rails APIs for example).

{
  "posts": [ { "id": 1 } ]
  "comments": [ { "id": 1, "post_id": 1 } ]
}
json-server db.json --fks _id
GET /posts/1/comments
Nezteb commented 7 years ago

@typicode I don't think that's what people in this thread are referring to. They want to be able to rename primary keys to something other than "id". The --id flag doesn't work for me either.

{
    "posts": [
        { "postId": 1, "title": "Hello world" },
        { "postId": 2, "title": "Hello other world" },
        { "postId": 3, "title": "Goodbye world" }
    ]
}

In this case, /posts/1 returns {}. I've also tried post_id with --id "_id".

zhenik commented 7 years ago

That works for me. Exact command that I use json-server mocked_db.json --id _id --port 3004 ?

chfw commented 6 years ago

Having the same need in the program too. It is solved by these:

const server = jsonServer.create();
const router = jsonServer.router('dummy.json'); 
router.db._.id = "yourCustomIdField";
server.use(router); 
FrancescoBorzi commented 6 years ago

It would be nice to have this feature. In particular it would be nice to be able to set different ID param names for different entities.

For example:

{
  "posts": [
    { "postId": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "commentId": 1, "body": "some comment", "postId": 1 }
  ]
}

in this case I would like to assign postId as id key for the "posts" entities, and commentId as id key for "comments" entities

fehrinjt commented 6 years ago

Has there been any traction on this? I'm very in favor of a solution to have an ids.json file that would map each resource to it's own custom ID.

Gabriel-Cheung commented 6 years ago

I overwrote the render function of router to return the custom id as I want without much effort. It is still storing the data with 'id', but returning with custom id.

import _ from 'lodash';

const idMappings = {
    posts: "postId",
    comments: "commentId",
};

router.render = (req, res) => {
    const entity = req.path.split('/')[1];
    let data;
    if(_.isArray(res.locals.data)) {
        data = _.map(res.locals.data, (item) => {
            item[idMappings[entity]] = `${item.id}`;
            delete item.id;
            return item;
        });
    } else {
        data = res.locals.data;
        data[idMappings[entity]] = `${data.id}`;
        delete data.id;
    }
    res.jsonp({
        data,
    });
};
tomeustace commented 6 years ago

I needed a quick way to keep my ids in sync, below is inspired by and similar to above but as middleware:

'use strict'
// idMappings.js

// read --ids as JSON, e.g. json-server db.json --ids '{ "posts": "postsId", "comments": "commentsId" }'
let index = process.argv.indexOf('--ids');
let idMappings = process.argv[index + 1];
idMappings = JSON.parse(idMappings);

module.exports = (req, res, next) => {
  if (req.method === 'POST' || req.method === 'PUT') {
    const entity = req.path.split('/')[1];
    req.body.id = req.body[idMappings[entity]];
  }
  next()
}

Run


json-server --watch db.json --ids '{"posts":"postsId", "comments": "commentsId"}' --middlewares idMapping.js
capJavert commented 6 years ago

My implementation, merging tomeustace and other ideas above. Put this in server.js or other file and run as node server.js. db.json will still contain "iternal id" column but each request and response will be remapped.

This was a quick and dirty fix, I think that this req path parsing could be improved to support more complex resource urls.

const jsonServer = require('json-server');
const server = jsonServer.create();
const path = require('path');
const router = jsonServer.router(path.join(__dirname, 'db.json'));
const middlewares = jsonServer.defaults();

// define primary key columns for each resource
const primaryKeys = {
  "countries": "code",
  "towns": "id",
  "access-policies": "accessPolicyId",
  "device-services": "id"
};

const port = 3200;

server.use(middlewares);
server.use(router);
server.listen(port, () => {
  console.log('Test server is running on port: ' + port)
});

router.render = (req, res) => {
  const resource = req.path.split('/')[1];
  let filteredResponse = res.locals.data;

  // remapping internal id columns to dedicated resource columns
  if (req.method === 'POST' || req.method === 'PUT') {
    req.body.id = req.body[primaryKeys[resource]];
  } else if (req.method === 'GET') {
    filteredResponse = filterResponse(resource, res.locals.data);
  }

  // optional response wrapping to data attribute
  res.jsonp({
    data: filteredResponse
  });
};

function filterResponse(resource, response) {
  // don't filter if primaryKey is same as internal
  if (primaryKeys[resource] === 'id') {
    return response;
  }

  if (response instanceof Array) {
    for (let i=0;i<response.length;i++) {
      delete response[i].id;
    }
  } else {
    delete response.id;
  }

  return response;
}
ausmurp commented 6 years ago

@typicode how can we use foreignKeySuffix when running json-server as module? Also can we add json-server.json config when running as module? I've added {"foreignKeySuffix":"_id"} in json-server.json but it's still not working..

549393092 commented 6 years ago

@Gabriel-Cheung Could you please tell me where should I put the source code that you mentioned?

GabrielCheung128 commented 6 years ago

@549393092 My reply was just the idea. For further implementations, you could refer to the two answers below, which already made it as middleware. Also, I think what you were asking about was this?

549393092 commented 6 years ago

@capJavert When adopt your code, there is one issue. if using GET /posts then it works fine. if using GET /posts/1 then it fails. The status code is 404. Then I console.log(res.locals.data) before call method filterResponse(), it returns { }. Did you encounter this problem before?

capJavert commented 6 years ago

@549393092 I am using that code on regular basis for CRUD service mockups and it works in both ways. Did you correctly define mappings for primary keys in primaryKeys variable. Also if you are filling data in your db.json by hand you should put auto increment id attribute no matter if that endpoint has it or not (it is required by internal mapping).

549393092 commented 6 years ago

@capJavert It works now. I forget to add id attribute in db.json as I thought it is not necessary. Thanks a lot for your help.

549393092 commented 6 years ago

@capJavert Still one thing to ask you. When input http://localhost:3000/posts/1 in the IE, it returns as below { "data": { postId: 1, ... } } but what I want is as below which is normal { postId: 1, ... } Do you know how to do it, thanks!

capJavert commented 6 years ago

@549393092 You can adjust response wrapping in this part of my code:

...
  res.jsonp({
    data: filteredResponse
  });
...
549393092 commented 6 years ago

@capJavert Yes, it works. Thank you very much!

eurochriskelly commented 6 years ago

For anyone coming here looking for a workaround (like me), something I found useful was to organize my mock data like this:

GET locahost:3000/cats Response:

[
  {  id: 1, catId: 1, name: 'felix'  },
  {  id: 2, catId: 2, name: 'garfield' },
]

GET locahost:3000/cats/2 Response:

{  id: 2, catId: 2, name: 'garfield' }

GET locahost:3000/dogs Response:

[
  {  id: 1, dogId: 2, name: 'fido' },
]

GET locahost:3000/dogs/1 Response:

{  id: 1, dogId: 2, data: 'fido' }

My API consumer uses the desired id (catId or dogId) and ignores id.

kyleo347 commented 5 years ago

In case you want to persist using the alternate IDs, I wrote a server.js that takes an ID mapping file and saves it using the other IDs. I used different paths, get// and put/, since I wasn't sure if json-server would let me override the defaults.

IdMap.json: { "post": "postId" }

server.js: `const jsonServer = require('json-server'); const server = jsonServer.create(); const router = jsonServer.router('./db.json'); const middlewares = jsonServer.defaults(); const port = process.env.PORT || 3000; var db = require('./db.json'); var idMap = require('./IdMap.json');

server.use(jsonServer.bodyParser); server.use(middlewares);

server.put('/put/:object/', (req, res) => { if (req.method === 'PUT') { let newObj = req.body; let name = req.params.object; let id = newObj[idMap[name]]; let find = {}; find[idMap[name]] = id; if (id && newObj) { if (router.db.get(name).find(find)) { res.status(200).jsonp( router.db.get(name).find(find).assign(newObj).value()); router.db.write(); } else { res.status(400).jsonp({ error: "Bad id" }); } } else { res.status(400).jsonp({ error: "No valid id" }); } } });

server.get('/get/:object/:id', (req, res) => { if (req.method === 'GET') { let id = Number(req.params.id) ? Number(req.params.id) : req.params.id; let name = req.params.object let find = {}; find[idMap[name]] = id; if (id) { let result = router.db.get(name).find(find) if (result.value()) { res.status(200).jsonp(result.value()); } else { res.status(400).jsonp({ error: "Bad id" }); } } else { res.status(400).jsonp({ error: "No valid id" }); } } });

server.use(router); server.listen(port);`

ShellOliver commented 5 years ago

A work around for add another id can be a middleware like:

module.exports = (req, res, next) => {
  const tableName = req.url.substring(1);
  res.req.body[tableName + "Id"] = res.req.body.id;
  next();
};
santosidauruk commented 5 years ago

it's weird but this works for me. I put the option for --id first before the --watch.

json-server --id _id --watch db.json => works json-server --watch db.json --id _id => doesnt work

Nezteb commented 5 years ago

I last posted on this issue in Fall 2017.

I have a test.json file that looks like:

{
    "posts": [
        { "post_id": 1, "title": "Hello world" },
        { "post_id": 2, "title": "Hello other world" },
        { "post_id": 3, "title": "Goodbye world" }
    ]
}

I run json-server test.json --id _id and go to localhost:3000/posts/1 and receive {} back. I also tried reorganizing the arguments.

The workaround I've used since 2017 is to go to localhost:3000/posts?post_id=1, but that's still not ideal.

I'm on version 0.14.2.

melwynfurtado commented 5 years ago

@Nezteb I think --id config option is meant for overwriting default id (key) and not as suffix to existing keys.

I wish we can have customized ids per entity/resource as suggested by @mertkahyaoglu.

Nezteb commented 5 years ago

The feature that user suggested doesn't exist:

Oh, it was just a suggestion. json-server does not have this feature, but it would be great to have. Sorry about the confusion.

I agree that it's a good idea. --foreignKeySuffix is an excellent addition, but having a --primaryKeySuffix as well would be nice. Although if the primary and foreign suffixes are the same, that'd cause issues.

navneetkhanuja commented 5 years ago

@santosidauruk I tried your way and it worked. Thanks. I am using 0.14.2 version json-server --id --watch db.json => works json-server --watch db.json --id => doesn't work

soham595 commented 4 years ago

Any solution for using custom id for a resource and that too for multiple resources with a single json-server?

acefei commented 4 years ago

In case you want to persist using the alternate IDs, I wrote a server.js that takes an ID mapping file and saves it using the other IDs. I used different paths, get// and put/, since I wasn't sure if json-server would let me override the defaults.

IdMap.json: { "post": "postId" }

server.js: `const jsonServer = require('json-server'); const server = jsonServer.create(); const router = jsonServer.router('./db.json'); const middlewares = jsonServer.defaults(); const port = process.env.PORT || 3000; var db = require('./db.json'); var idMap = require('./IdMap.json');

server.use(jsonServer.bodyParser); server.use(middlewares);

server.put('/put/:object/', (req, res) => { if (req.method === 'PUT') { let newObj = req.body; let name = req.params.object; let id = newObj[idMap[name]]; let find = {}; find[idMap[name]] = id; if (id && newObj) { if (router.db.get(name).find(find)) { res.status(200).jsonp( router.db.get(name).find(find).assign(newObj).value()); router.db.write(); } else { res.status(400).jsonp({ error: "Bad id" }); } } else { res.status(400).jsonp({ error: "No valid id" }); } } });

server.get('/get/:object/:id', (req, res) => { if (req.method === 'GET') { let id = Number(req.params.id) ? Number(req.params.id) : req.params.id; let name = req.params.object let find = {}; find[idMap[name]] = id; if (id) { let result = router.db.get(name).find(find) if (result.value()) { res.status(200).jsonp(result.value()); } else { res.status(400).jsonp({ error: "Bad id" }); } } else { res.status(400).jsonp({ error: "No valid id" }); } } });

server.use(router); server.listen(port);`

This informative answer is what I want.

// deal with custom resource id for each resource
const idMappings = {
  customers: 'customerid',
  updates: 'updateId',
};

server.get(`/:resource/:id`, (req, res) => {
  const { resource, id: resourceId } = req.params;

  const query =
    resource in idMappings
      ? {
          [idMappings[resource]]: resourceId,
        }
      : {
          id: resourceId,
        };

  const result = router.db
    .get(resource)
    .find(query)
    .value();

  return res.status(200).jsonp(result);
});
zhangweiHello commented 4 years ago

I have the same problem, but I don't think this is what JSON-Server should do .You can use other frameworks like aok.js
json-server is great , i use it only when do api mocking

ArchDL commented 3 years ago

I've came up with using custom routes in cases where I need custom id: json-server --watch db.json --routes routes.json

routes.json: { "/customIDroute/:cusomID" : "/customIDroute?cusomID=:cusomID" }