I have RESTful API built on top of a MongoDB store, so it's nice that you can store arrays. It's straightforward to create a new resource like this:
POST /users
{
items: [
1001, 1002, 1003
]
}
But how would the HTTP endpoint for adding a new item or removing an item would look like?
Right now, I have to specify the entire array, including elements that I don't want to touch:
PATCH /users/{id}
{
name: 'Bruce Wayne',
items: [
1001, 1002
]
}
Or pass in a mongodb query directly:
PATCH /users/{id}?query[$push][items]=1003
Is there a better way to do this?
Edit:
I like how StackMob's API does it. How do I update the name and remove an element from items at the same time though? For example, when I'm updating a bunch of the user's details on an admin dashboard? I don't think replacing the entire array is a good idea in mongodb?
Passing a mongodb query seems like a bad idea. Depending on your backend implementation it could lead to an attacker doing bad things to your data as in SQL Injection
You can model the modification of an attribute on a resource with PUT or PATCH with some limitations:
When using PUT the client is expected to send the whole representation of the resource. IT works for you but it may be cumbersome.
When using PATCH the client is expected to send the attributes that are intended to change, instead of the whole resource. Yet, you have to send the whole value, not just the additions or deletions of items to the value. Again it works but you are not in love with it.
I think you are looking for a way to model adding and removing items to the array:
I would model the array as a resource on its own: /users/:id/items
Accept POSTto add an item to the array and DELETE to remove from the array.
It's simple and RESTful.
As per the REST standards to create and delete a new request -->
POST -Create a new resource in a collection
and
DELETE -Delete a resource
I can give you an example of how the high-level HTTP endpoint in java looks like using Jersey.
You can have a Resource class with the HTTP Path specified and specific Paths for methods doing different operations.
So the URL could look like --
/rest/MyResource/Resource accompanied by a request JSON or XML(that contains your input data)
Here is a sample Resource class that would be your entry point(ofcourse you would have to do your configuration in web.xml to do URL mapping for this class) -->
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.json.JSONObject;
public class SampleRESTServiceResource {
/**
* #param incomingJsonString
* #return Response
*/
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createNewResource(JSONObject myJson) {
// Do a call to a DAO Implementation that does a JDBC call to insert into Mongo based on JSON
return null;
}
/**
* #param incomingJsonString
* #return Return response
*/
#DELETE
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response deleteResource(JSONObject myJson) {
// Do a call to a DAO Implementation that does a JDBC call to delete resource from Mongo based on JSON
return null;
}
}
If you want to try out an example you can refer to this page -->
https://www.ibm.com/developerworks/library/wa-aj-tomcat/
Related
Before you start reading: I have looked at the GraphQL documentation, but my usecase is so specific and I only need the data once, and therefore I allow myself to ask the community for help on this one to save some time and frustration (not planning to learn GraphQL in the future)
Intro
I am a CS student developing an app for Flutter on the side, where I need information about the name and location of every bus stop in a specific county in Norway. Luckily, there's an open GraphQL API for this (API URL: https://api.entur.io/stop-places/v1/graphql). The thing is, I don't know how to query a GraphQL API, and I do not want to spend time learning it as I am only going to fetch the data once and be done with it.
Here's the IDE for the API: https://api.entur.io/stop-places/v1/ide
And this is the exact query I want to perform as I want to fetch bus stops located in the county of Trondheim:
{
stopPlace(stopPlaceType: onstreetBus, countyReference: "Trondheim") {
name {
value
}
... on StopPlace {
quays {
geometry {
coordinates
}
}
}
}
}
The problem with this query though, is that I don't get any data when passing "Trondheim" to the countyReference (without countyReference I get the data, but not for Trondheim). I've tried using the official municipal number for the county as well without any luck, and the documentation of the API is rather poor... Maybe this is something I'll have to contact the people responsible for the API to figure out, which shouldn't be a problem.
But now back to the real problem - how can I make this query using the GraphQL package for Dart? Here's the package I'm planning to use: (https://pub.dev/packages/graphql)
I want to create a bus stop object for each bus stop, and I want to put them all in a list. Here is my bus stop model:
class BusStop with ChangeNotifier {
final String id;
final String name;
final LatLng location;
BusStop({
this.id,
this.name,
this.location
});
}
When it comes to authentication, here's what the documentation says:
This API is open under NLOD licence, however, it is required that all consumers identify themselves by using the header ET-Client-Name. Entur will deploy strict rate-limiting policies on API-consumers who do not identify with a header and reserves the right to block unidentified consumers. The structure of ET-Client-Name should be: "company - application"
Header examples: "brakar - journeyplanner" "fosen_utvikling - departureboard" "norway_bussekspress - nwy-app"
Link to API documentation: https://developer.entur.org/pages-nsr-nsr
Would be great to know how I should go about this as well! I'm grateful for every answers to this, I know I am being lazy here as of learning GraphQL, but for my usecase I thought it would take less time and frustration by asking here!
Getting the query right
First of all you seem to have GraphQL quite figured out. There isn't really much more to it than what you are doing. What queries an API supports depends on the API. The problem you seem to have is more related to the specific API that you are using. I might have figured the right query out for you and if not I will quickly explain what I did and maybe you can improve the query yourself:
{
stopPlace(stopPlaceType: onstreetBus, municipalityReference: "KVE:TopographicPlace:5001") {
name {
value
}
... on StopPlace {
quays {
geometry {
coordinates
}
}
}
}
}
So to get to this I started finding out more about "Trondheim" bei using the topographicPlace query.
{
topographicPlace(query: "Trondheim") {
id
name {
value
}
topographicPlaceType
parentTopographicPlace {
id
name {
value
}
}
}
}
If you do that you will see that "Trondheim" is not a county according to the API: "topographicPlaceType": "municipality". I have no idea what municipality is but the is a different filter for this type on the query that you provided. Then putting "Trondheim" there didn't yield any results so I tried the ID of Trondheim. This now gives me a bunch of results.
About the GraphQL client that you are using:
This seems to be an "Apollo Client" clone in Dart. Apollo Client is a heavy piece of software that comes with a lot of awesome features when used in a frontend application. You probably just want to make a single GraphQL request from a backend. I would recommend using a simple HTTP client to send a POST request to the GraphQL API and a JSON body (don't forget content type header) with the following properties: query containing the query string from above and variables a JSON object mapping variable names to values (only needed if you decide to add variables to your query.
Overview
I'm using Ember data and have a JSONAPI. Everything works fine until I have a more complex object (let's say an invoice for a generic concept) with an array of items called lineEntries. The line entries are not mapped directly to a table so need to be stored as raw JSON object data. The line entry model also contains default and computed values. I wish to store the list data as a JSON object and then when loaded back from the store that I can manipulate it as normal in Ember as an array of my model.
What I've tried
I've looked at and tried several approaches, the best appear to be (open to suggestions here!):
Fragments
Replace problem models with fragments
I've tried making the line entry model a fragment and then referencing the fragment on the invoice model as a fragmentArray. Line entries add to the array as normal but default values don't work (should they?). It creates the object and I can store it in the backend but when I return it, it fails with either a normalisation issue or a serialiser issue. Can anyone state the format the data be returned in? It's confusing as normalising the data seems to require JSONAPI but the fragment requires JSON serialiser. I've tried several combinations but no luck so far. My line entries don't have actual ids as the data is saved and loaded as a block. Is this an issue?
DS.EmbeddedRecordsMixin
Although not supported in JSONAPI, it sounds possible to use JSONAPI and then switch to JSONSerializer or RESTSerializer for the problem models. If this is possible could someone give me a working example and the JSON format that should be returned by the API? I have header authorisation and other such data so would I still be able to set this at the application level for all request not using my JSONAPI?
Ember-data-save-relationships
I found an add on here that provides an add on to do this. It seems more involved than the other approaches but when I've tried this I can send the data up by setting a the data as embedded. Great! But although it saves it doesn't unwrap it correct and I'm back with the same issues.
Custom serialiser
Replace the models serialiser with something that takes the data and sends it as plain JSON data and then deserialises back into something Ember can use. This sounds similar to the above but I do the heavy lifting. The only reason to do this is because all examples for the above solutions are quite light and don't really show how to set this up with an actual JSONAPI set up that would need it.
Where I am and what I need
Basically all approaches lead to saving the JSON fine but the return JSON from the server not being the correct format or the deserialisation failing but it's unclear what it should be or what needs to change without breaking the existing JSONAPI models that work fine.
If anyone know the format for return API data it may resolve this. I've tried JSONAPI with lineEntries returning the same format as it saved. I've tried placing relationship sections like the add on suggested and I've also tried placing relationship only data against the entries and an include section with all the references. Any help on this would be great as I've learned a lot through this but deadlines a looming and I can't see a viable solution that doesn't break as much as it fixes.
If you are looking for return format for relational data from the API server you need to make sure of the following:
Make sure the relationship is defined in the ember model
Return all successes with a status code of 200
From there you need to make sure you return relational data correctly. If you've set the ember model for the relationship to {async: true} you need only return the id of the relational model - which should also be defined in ember. If you do not set {async: true}, ember expects all relational data to be included.
return data with relationships in JSON API specification
Example:
models\unicorn.js in ember:
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.belongsTo('user', {async: true}),
staticrace: DS.belongsTo('staticrace',{async: true}),
unicornName: DS.attr('string'),
unicornLevel: DS.attr('number'),
experience: DS.attr('number'),
hatchesAt: DS.attr('number'),
isHatched: DS.attr('boolean'),
raceEndsAt: DS.attr('number'),
isRacing: DS.attr('boolean'),
});
in routes\unicorns.js on the api server on GET/:id:
var jsonObject = {
"data": {
"type": "unicorn",
"id": unicorn.dataValues.id,
"attributes": {
"unicorn-name" : unicorn.dataValues.unicornName,
"unicorn-level" : unicorn.dataValues.unicornLevel,
"experience" : unicorn.dataValues.experience,
"hatches-at" : unicorn.dataValues.hatchesAt,
"is-hatched" : unicorn.dataValues.isHatched,
"raceEndsAt" : unicorn.dataValues.raceEndsAt,
"isRacing" : unicorn.dataValues.isRacing
},
"relationships": {
"staticrace": {
"data": {"type": "staticrace", "id" : unicorn.dataValues.staticRaceId}
},
"user":{
"data": {"type": "user", "id" : unicorn.dataValues.userId}
}
}
}
}
res.status(200).json(jsonObject);
In ember, you can call this by chaining model functions. For example when this unicorn goes to race in controllers\unicornracer.js:
raceUnicorn() {
if (this.get('unicornId') === '') {return false}
else {
return this.store.findRecord('unicorn', this.get('unicornId', { backgroundReload: false})).then(unicorn => {
return this.store.findRecord('staticrace', this.get('raceId')).then(staticrace => {
if (unicorn.getProperties('unicornLevel').unicornLevel >= staticrace.getProperties('raceMinimumLevel').raceMinimumLevel) {
unicorn.set('isRacing', true);
unicorn.set('staticrace', staticrace);
unicorn.set('raceEndsAt', Math.floor(Date.now()/1000) + staticrace.get('duration'))
this.set('unicornId', '');
return unicorn.save();
}
else {return false;}
});
});
}
}
The above code sends a PATCH to the api server route unicorns/:id
Final note about GET,POST,DELETE,PATCH:
GET assumes you are getting ALL of the information associated with a model (the example above shows a GET response). This is associated with model.findRecord (GET/:id)(expects one record), model.findAll(GET/)(expects an array of records), model.query(GET/?query=&string=)(expects an array of records), model.queryRecord(GET/?query=&string=)(expects one record)
POST assumes you at least return at least what you POST to the api server from ember , but can also return additional information you created on the apiServer side such as createdAt dates. If the data returned is different from what you used to create the model, it'll update the created model with the returned information. This is associated with model.createRecord(POST/)(expects one record).
DELETE assumes you return the type, and the id of the deleted object, not data or relationships. This is associated with model.deleteRecord(DELETE/:id)(expects one record).
PATCH assumes you return at least what information was changed. If you only change one field, for instance in my unicorn model, the unicornName, it would only PATCH the following:
{
data: {
"type":"unicorn",
"id": req.params.id,
"attributes": {
"unicorn-name" : "This is a new name!"
}
}
}
So it only expects a returned response of at least that, but like POST, you can return other changed items!
I hope this answers your questions about the JSON API adapter. Most of this information was originally gleamed by reading over the specification at http://jsonapi.org/format/ and the ember implementation documentation at https://emberjs.com/api/data/classes/DS.JSONAPIAdapter.html
I have a REST interface endpoint like
POST /items/12345/actions
I utilize a generic actions sub collection to be apply to apply changes to 12345 which are not easily mapped to the content or direct other sub collections of it.
My question is the following: Since there could be multiple different action types I identify the action by a JSON property of the content of an uploaded document.
How do I select a action by a part of the JSON body of the request. Is there something possible like...
[Route("api/v1/items")
public class ItemsController : Controller
{
[HttpPost("{id}/actions")]
[CheckJsonBody("type", "ActionA")]
public ActionResult DoActionA(int id, ActionA a)
{
// do something
}
[HttpPost("{id}/actions")]
[CheckJsonBody("type", "ActionB")]
public ActionResult DoActionB(int id, ActionB b)
{
// do something
}
}
The request would look like ...
{
"type": "ActionA",
"abc": "xyz"
}
I have digged myself up into the code till Microsoft.AspNetCore.Mvc.ActionConstraints.ActionMethodSelectorAttribute (GitHub).
However starting from there, I am a bit lost to reach a high-performance solution. Do I need to decode the body or is that something which is already done at that time the constraint is evaluated?
ps: And yes, I know I could handle them in one action and do a switch on the "type" property.
An ASP.NET team member was so friendly to direct me to an answer: In the ActionMethodSelectorAttribute you can read the body into a memory stream, read till the property for the selection filter. Then you seek the memory stream to zero and replace it in the request (for later model binding). You can cache the criteria value in HttpContext.Items to speed it up if you use the same property for multiple actions.
I have a data source provider :
public class DSProvider
{
public IQueryable<Product> Products
{
get
{
return _repo.Products.AsQueryable();
}
}
}
The repository in the above example currently gets ALL the records (of Products) from DB and then applies the filters, this just does not sound right if you had 50000 requests/sec from a website.How can you limit the repository to just return required info from DB without converting the service to a tightly coupled request option i.e. opposite of what you try to achieve by using oData?
So to summarize I would like to know if its possible to query the DB on the oData options supplied by the user so that my request does not always have to get all products and then apply filters of oData.
I found out after doing a small POC that Entity framework takes care of building dynamic query based on the request.
I'm trying to code a class handling serialization of documents by reading their metadata. I got inspired by this implementation for entities with Doctrine ORM and modified it to match how Doctrine ODM handles documents. Unfortunatly something is not working correctly as one document is never serialized more than once even if it is refered a 2nd time thus resulting on incomplete serialization.
For example, it outputs this (in json) for a user1 (see User document) that belongs to some place1 (see Place document). Then it outputs the place and the users belonging to it where we should see the user1 again but we don't :
{
id: "505cac0d6803fa1e15000004",
login: "user1",
places: [
{
id: "505cac0d6803fa1e15000005",
code: "place1",
users: [
{
id: "505c862c6803fa6812000000",
login: "user2"
}
]
}
]
}
I guess it could be related to something preventing circular references but is there a way around it ?
Also, i'm using this in a ZF2 application, would there be a better way to implement this using the ZF2 Serializer ?
Thanks for your help.
I have a serializer already written for DoctrineODM. You can find it in http://github.com/superdweebie/DoctrineExtensions - look in lib/Sds/DoctrineExtensions/Serializer.
If you are are using zf2, then you might also like http://github.com/superdweebie/DoctrineExtensionsModule, which configures DoctrineExtensions for use in zf2.
To use the Module, install it with composer, as you would any other module. Then add the following to your zf2 config:
'sds' => [
'doctrineExtensions' => [
'extensionConfigs' => [
'Sds\DoctrineExtensions\Serializer' => null,
),
),
),
To get the serializer use:
$serializer = $serivceLocator->get('Sds\DoctrineExtensions\Serializer');
To use the serializer:
$array = $serializer->toArray($document)
$json = $serializer->toJson($document)
$document = $serializer->fromArray($array)
$document = $serializer->fromJson($json)
There are also some extra annotations available to control serialization, if you want to use them:
#Sds\Setter - specify a non standard setter for a property
#Sds\Getter - specify a non standard getter fora property
#Sds\Serializer(#Sds\Ignore) - ignore a property when serializing
It's all still a work in progress, so any comments/improvements would be much appreciated. As you come across issues with these libs, just log them on github and they will get addressed promptly.
Finally a note on serializing embedded documents and referenced documents - embedded documents should be serialized with their parent, while referenced documents should not. This reflects the way data is saved in the db. It also means circular references are not a problem.
Update
I've pushed updates to Sds/DoctrineExtensions/Serializer so that it can now handle references properly. The following three (five) methods have been updated:
toArray/toJson
fromArray/fromJson
applySerializeMetadataToArray
The first two are self explainitory - the last is to allow serialization rules to be applied without having to hydrate db results into documents.
By default references will be serialized to an array like this:
[$ref: 'CollectionName/DocumentId']
The $ref style of referencing is what Mongo uses internally, so it seemed appropriate. The format of the reference is given with the expectation it could be used as a URL to a REST API.
The default behaviour can be overridden by defineing an alternative ReferenceSerializer like this:
/**
* #ODM\ReferenceMany(targetDocument="MyTargetDocument")
* #Sds\Serializer(#Sds\ReferenceSerializer('MyAlternativeSerializer'))
*/
protected $myDocumentProperty;
One alternate ReferenceSerializer is already included with the lib. It is the eager serializer - it will serialize references as if they were embedded documents. It can be used like this:
/**
* #ODM\ReferenceMany(targetDocument="MyTargetDocument")
* #Sds\Serializer(#Sds\ReferenceSerializer('Sds\DoctrineExtensions\Serializer\Reference\Eager'))
*/
protected $myDocumentProperty;
Or an alternate shorthand annotation is provided:
/**
* #ODM\ReferenceMany(targetDocument="MyTargetDocument")
* #Sds\Serializer(#Sds\Eager))
*/
protected $myDocumentProperty;
Alternate ReferenceSerializers must implement Sds\DoctrineExtensions\Serializer\Reference\ReferenceSerializerInterface
Also, I cleaned up the ignore annotation, so the following annotations can be added to properties to give more fine grained control of serialization:
#Sds\Serializer(#Sds\Ignore('ignore_when_serializing'))
#Sds\Serializer(#Sds\Ignore('ignore_when_unserializing'))
#Sds\Serializer(#Sds\Ignore('ignore_always'))
#Sds\Serializer(#Sds\Ignore('ignore_never'))
For example, put #Sds\Serializer(#Sds\Ignore('ignore_when_serializing')) on an email property - it means that the email can be sent upto the server for update, but can never be serialized down to the client for security.
And lastly, if you hadn't noticed, sds annotations support inheritance and overriding, so they play nice with complex document structures.
Another very simple, framework independent way to transforming Doctrine ODM Document to Array or JSON - http://ajaxray.com/blog/converting-doctrine-mongodb-document-tojson-or-toarray
This solution gives you a Trait that provides toArray() and toJSON() functions for your ODM Documents. After useing the trait in your Document, you can do -
<?php
// Assuming in a Symfony2 Controller
// If you're not, then make your DocmentManager as you want
$dm = $this->get('doctrine_mongodb')->getManager();
$report = $dm->getRepository('YourCoreBundle:Report')->find($id);
// Will return simple PHP array
$docArray = $report->toArray();
// Will return JSON string
$docJSON = $report->toJSON();
BTW, it will work only on PHP 5.4 and above.