I am new to node.js and have my first node.js Restful API built in hapi.js framework. All the services do is basically doing database query. An example of the services is like this:
let myservice = {
method: "POST",
path: "/updateRule",
config: {
handler: (request, reply) => {
updateRule(request.payload)
.then((result) => {
reply(successResponse(request, result));
})
.catch((err) => reply(failResponse(request, err)).code(500));
},
validate: {
payload: {
ruleId: joi.number().required(),
ruleName: joi.string().required(),
ruleDesc: joi.string().required()
}
},
auth: "jwt",
tags: ["api", "a3i"]
},
}
updateRule(input): Promise<any> {
return new Promise((resolve, reject) => {
let query = `select a3i.update_rule(p_rul_id := ${input.ruleId}, p_rul_name := '${input.ruleName}', p_rul_desc := '${input.ruleDesc}')`;
postgresQuery(lbPostgres, query, (data, commit, rollback) => {
try {
let count = data.rows[0].update_rule.count;
if (count === 1) {
let ruleId = data.rows[0].update_rule.result[0];
let payload: SuccessPayload = {
type: "string",
content: `Rule ${ruleId} has been updated`
};
commit();
resolve(payload);
} else {
let thisErr = new Error("No rule can be found.");
thisErr.name = "4003";
throw thisErr;
}
}
catch (err) {
rollback();
if (err.name === "4003") {
reject(detailError(4003, err.message));
} else {
reject(detailError(4001, err.message));
}
}
}, reject);
});
}
As you can see, when the service is called, it evokes a database call (query) and updates specified row in database table. Similarly, I have other services named createRule/deleteRule creating/deleting records in database table. In my opinion, the difference between the services is doing different database query. I read this post PUT vs. POST in REST but couldn't see any difference of POST and PUT in my case.
Here are my questions:
What HTTP method should I used in this case?
Most of Restful API examples (for example https://www.codementor.io/olatundegaruba/nodejs-restful-apis-in-10-minutes-q0sgsfhbd) use the same URL with different HTTP methods to do different operations on same "resource", which in my opinion is usually a database table. What's the benefit of this architecture compared with my practice in which one URL only has one HTTP method and only do one type of operation?
I know this question does not refer to a problem and is not specific. Some people may give it a down-vote. But as a beginner I really want to know what's a typical Restful API and make sure my API is "best practice". Please help!
If the resource already exists and thus you have a specific URI to that exact resource and you want to update it, then use PUT.
If the resource does not exist yet and you want to create it and you will let the server pick the URI that represents that new resource, then use POST and the POST URI will be a generic "create new resource" URI, not a URI to a specific resource and it will create the URI that represents that resource.
You can also use PUT to create a new resource if the caller is going to create the resource URI that represents the new resource. In that case, you would just PUT to that new resource and, if a resource with that URI already exists, it would be updated, if not, it would be created.
You do not have to support both. You can decide to make your api work in a way that you just use one or the other.
In your specific case, an update of a specific row in your database that already exists would pretty much always be a PUT because it already exists so you're doing a PUT to a specific URI that represents that row.
What's the benefit of this architecture compared with my practice in which one URL only has one HTTP method and only do one type of operation?
It's really up to you how you want to present your API. The general concept behind REST is that you have several components:
resource identifier
data
method
In some cases, the method can be subsumed by GET, PUT, POST or DELETE so you just need the resource identifier, data and GET, PUT, POST or DELETE.
In other cases or other designs, the method is more detailed than can be expressed in just a PUT or POST, so you have a method actually in the URL in which case, you may not need the distinction between PUT and POST as much.
For example, an action might be "buy". While you could capture that in a POST where the method is implied by the rest of the URL, you may want to actually POST to a URL that has a method in it: /buy for clarity and then you may use that same endpoint prefix with other methods such as /addToCart, etc... It really depends upon what the objects are in your REST design and what operations you want to surface on them. Sometimes, the objects lends themselves to just GET, PUT, POST and DELETE and sometimes, you want more info in the URL as to the specific operation to be carried out on that resource.
If you want to be Rest compliant, you can just use Post and Get.
If you want to be Restfull, you need to base your method on the CRUD
Create -> Post
Read -> Get
Update -> Put or Patch
Delete -> Delete
About building a full API, using method on the same URL could be easier to build / understand. All queries about your user will be on the user url and not user/get, user/add, user/update ... It let you have the same functionality, without too much different URL.
When you build an API, you will want to have some logs, for stats analysis and other stuff. This way, if you split with your method, you can just have a filter to logs how many Post requests, or Get requests.
In fact, you could build an API only with Get requests too. But spliting with methods and URL is the best way to avoid complexes URL (or URL with too much action name) and to have an easiest way to log every requests going through your API
- List item
Level 1 is Rest
Level 2 is Restfull
Level 3 is Hateoas
You should find more informations inside some books or articles written by Martin Fowler
What I usually do is use "POST" for creating a new resource, and use "PUT" for updating an already existing resource.
For your second question, yes most API's use the same URL to do different things on the same resource. That could be because of security where you don't want to expose what you are doing in your URL's (/delete for example). Also, many frameworks generate an auto URL for a resource (Object Class), that is then differentiated on the request method. People just don't tend to use custom URL's for those.
Related
I am currently writing a Lambda authorizer for an AWS AppSync API, however the authorization depends on the target resource being accessed.
Every resource has their own ACL listing the users and conditions for allowing access to it.
Currently the best I could find would be to get the identity of the caller, look at all the ACLs, and authorize the call while denying access to all the other resources, what's not only highly inefficient, but also extremely impractical, if not impossible.
The solution I had originally came up with was to get the target resource, retrieve the ACL and check if the user fits the specified criteria. The problem is that I am unable to reliably define what's the target resource. What I get from AWS is a request like this:
{
"authorizationToken": "ExampleAUTHtoken123123123",
"requestContext": {
"apiId": "aaaaaa123123123example123",
"accountId": "111122223333",
"requestId": "f4081827-1111-4444-5555-5cf4695f339f",
"queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n",
"operationName": "MyQuery",
"variables": {}
}
}
So, I only have the query string and variables, leaving the actual parsing of this to me. I got to convert it to an AST using graphql-js, but it's still extremely verbose and most importantly, it's structure varies greatly.
My first code to retrieve the target worked for the AppSync console queries, but not the Amplify Front-End, for example. I also can't rely on something as simple as the variable name, as an attacker could quite easily craft a query with an arbitrary name, or even not use variables at all.
I thought about implementing this authorization logic within Lambda Resolvers, what should be simpler in a way, but would require me to use resolvers as authorizers, what doesn't seem ideal, and implement the entire resolver logic when I just want the most trivial possible resolvers.
Ideally I'd like something like this:
/* Schema:
type Query {
operationName(key: KEY!): responseType
}*/
/* Query:
query abitraryQueryName($var1: KEY!) {
operationName(key: $var1) {
field1
field2
}
}*/
/* Variables:
{ "var1": "value1" } */
parsedQuery = {
operation: "operationName",
params: { "key": "value1" },
fields: [ "field1", "field2" ]
};
Is there any way to resolve/simplify the queries from GraphQL to JSON/similar in a way that this information can be easily extracted?
Well, couldn't find anything on it, so I made something myself.
On the off chance someone needs something similar, here's the gist with the code I used: https://gist.github.com/Iorpim/6544dad46060522dd0b17477871bc434
I didn't make it a proper full lib, as it's a very specific use case and it's likely a one-off, and I am also not sure how reliable it is, but it solves my problem!
I'm using aws-appsync in a Node.js client to keep a cached list of data items. This cache must be available at all times, including when not connected to the internet.
When my Node app starts, it calls a query which returns the entire list of items from the AppSync data source. This is cached by Apollo's cache storage, which allows future queries (using the same GraphQL query) to be made using only the cache.
The app also makes a subscription to the mutations which are able to modify the list on other clients. When an item in the list is changed, the new data is sent to the app. This can trigger the original query for the entire list to be re-fetched, thus keeping the cache up to date.
Fetching the entire list when only one item has changed is not efficient. How can I keep the cache up to date, while minimising the amount of data that has to be fetched on each change?
The solution must provide a single point to access cached data. This can either be a GraphQL query or access to the cache store directly. However, using results from multiple queries is not an option.
The Apollo documentation hints that this should be possible:
In some cases, just using [automatic store updates] is not enough for your application ... to update correctly. For example, if you want to add something to a list of objects without refetching the entire list ... Apollo Client cannot update existing queries for you.
The alternatives it suggests are refetching (essentially what I described above) and using an update callback to manually update the cached query results in the store.
Using update gives you full control over the cache, allowing you to make changes to your data model in response to a mutation in any way you like. update is the recommended way of updating the cache after a query.
However, here it is referring to mutations made by the same client, rather than syncing using between clients using subscriptions. The update callback option doesn't appear to be available to a subscription (which provides the updated item data) or a query (which could fetch the updated item data).
As long as your subscription includes the full resource that was added, it should be possible by reading from and writing to the cache directly. Let's assume we have a subscription like this one from the docs:
const COMMENTS_SUBSCRIPTION = gql`
subscription onCommentAdded {
commentAdded {
id
content
}
}
`;
The Subscription component includes a onSubscriptionData prop, so we should be able to do something along these lines:
<Subscription
subscription={COMMENTS_SUBSCRIPTION}
onSubscriptionData={({ client, subscriptionData: { data, error } }) => {
if (!data) return
const current = client.readQuery({ query: COMMENTS_QUERY })
client.writeQuery({
query: COMMENTS_QUERY,
data: {
comments: [...current.comments, data.commentAdded],
},
})
}}
/>
Or, if you're using plain JavaScript instead of React:
const observable = client.subscribe({ query: COMMENTS_SUBSCRIPTION })
observable.subscribe({
next: (data) => {
if (!data) return
const current = client.readQuery({ query: COMMENTS_QUERY })
client.writeQuery({
query: COMMENTS_QUERY,
data: {
comments: [...current.comments, data.commentAdded],
},
})
},
complete: console.log,
error: console.error
})
I would like to store a value in the config file and look it up in the design document for comparing against update values. I'm sure I have seen this but, for the life of me, I can't seem to remember how to do this.
UPDATE
I realize (after the first answer) that there was more than one way to interpret my question. Hopefully this example clears it up a little. Given a configuration:
curl -X PUT http://localhost:5984/_config/shared/token -d '"0123456789"'
I then want to be able to look it up in my design document
{
"_id": "_design/loadsecrets",
"validate_doc_update": {
"test": function (newDoc,oldDoc) {
if (newDoc.supersecret != magicobject.config.shared.token){
throw({unauthorized:"You don't know the super secret"});
}
}
}
}
It's the abilitly to do something like the magicobject.config.shared.token that I am looking for.
UPDATE 2
Another potentially useful (contrived) scenario
curl -X PUT http://trustedemployee:5984/_config/eventlogger/detaillevel -d '"0"'
curl -X PUT http://employee:5984/_config/eventlogger/detaillevel -d '"2"'
curl -X PUT http://vicepresident:5984/_config/eventlogger/detaillevel -d '"10"'
Then on devices tracking employee behaviour:
{
"_id": "_design/logger",
"updates": {
"logger": function (doc,req) {
if (!doc) {
doc = {_id:req.id};
}
if(req.level < magicobject.config.eventlogger.detaillevel ){
doc.details = req.details;
}
return [doc, req.details];
}
}
}
Here's a follow-up to my last answer with more general info:
There is no general way to use configuration, because CouchDB is designed with scalability, stability and predictability in mind. It has been designed using many principles of functional programming and pure functions, avoiding side effects as much as possible. This is a Good Thing™.
However, each type of function has additional parameters that you can use, depending on the context the function is called with:
show, list, update and filter functions are executed for each request, so they get the request object. Here you have the req.secObj and req.userCtx to (ab)use for common configuration. Also, AFAIK the this keyword is set to the current design document, so you can use the design doc to get common configuration (at least up to CouchDB 1.6 it worked).
view functions (map, reduce) don't have additional parameters, because the results of a view are written to disk and reused in subsequent calls. map functions must be pure (so don't use e.g. Math.random()). For shared configuration across view functions within a single design doc you can use CommonJS require(), but only within the views.lib key.
validate doc update functions are not necessarily executed within a user-triggered http request (they are called before each write, which might not be triggered only via http). So they have the userCtx and secObj added as separate parameters in their function signature.
So to sum up, you can use the following places for configuration:
userCtx for user-specific config. Use a special role (e.g. with a prefix) for storing small config bits. For example superLogin does this.
secObj for database-wide config. Use a special member name for small bits (as you should normally use roles instead of explicit user names, secObj.members.names or secObj.admins.names is a good place).
the design doc itself for design-doc-wide config. Best use the this.views.lib.config for this, as you can also read this key from within views. But keep in mind that all views are invalidated as soon as you change this key. So if the view results will stay the same no matter what the config values are, it might be better to use a this.config key.
Hope this helps! I can also add examples if you wish.
I think I know what you're talking about, and if I'm right then what you are asking for is no longer possible. (at least in v1.6 and v2.0, I'm not sure when this feature was removed)
There was a lesser-known trick that allowed a view/show/list/validation/etc function to access the parent design document as this in your function. For example:
{
"_id": "_design/hello-world",
"config": {
"PI": 3.14
},
"views": {
"test": {
"map": "function (doc) { emit(this.config.PI); })"
}
}
}
This was a really crazy idea, and I imagine it was removed because it created a circular dependency between the design document and the code of the view that made the process of invalidating/rebuilding a view index a very tricky affair.
I remember using this trick at some point in the distant past, but the feature is definitely gone now. (and likely to never return)
For your special use-case (validating a document with a secret token), there might be a workaround, but I'm not sure if the token might leak in some place. It all depends what your security requirements are.
You could abuse the 4th parameter to validate_doc_update, the securityObject (see the CouchDB docs) to store the secret token as the first admin name:
{
"test": "function (newDoc, oldDoc, userCtx, secObj) {
var token = secObj.admins.names[0];
if (newDoc.supersecret != token) {
throw({unauthorized:"You don't know the super secret"});
}
}"
}
So if you set the db's security object to {admins: {names: ["s3cr3t-t0k3n"], roles: ["_admin"]}}, you have to pass 's3cr3t-t0k3n' as the doc's supersecret property.
This is obviously a dirty hack, but as far as I remember, the security object may only be read or modified by admins, you wouldn't immediately leak your token to the public. But consider adding a separate layer between the CouchDB and your caller if you need "real" security.
I need a help from you about REST Arch.
I've a resource and I can retrieve it with the classical GET /resource/ID URI, but this resource has an alias and someone want to GET this resource by calling it via alias.
There is a good way to do so by calling a GET /resource/?alias=x, take the ID and then go to the details /resource/ID.
Do you have any good idea about other ways to do this?
Thanks in advance
There is nothing wrong with a resource having two URIs (or two URIs pointing to the same resource, to put it another way). For example
GET www.myweatherapi.com/2013/11/18/rainfall
GET www.myweatherapi.com/today/rainfall
can both point to the same resource. You could say the latter is an alias of the former, or vice versa, it doesn't really matter, they both identify the same resource. You don't need to start explicitly labeling something as an alias of something else.
If the alias is temporary and may be gone in the future you could use the 307 response, temporary redirect. This tells the client that they should go to a different URI to find the resource, but not to assume that will be true in the future (eg limit how long you cache this).
As an aside, the client should not construct URIs, the server should return a content type format (HTML, JSON etc) that contains a way to identify the resources the client wants along with the URI of where to find them. For example a link in HTML saying "Todays Rainfall" with the URI to that resource. The user follows that link if they want todays rainfall
If you want to stay within the constraints of the REST architecture, you definitely need to stay with the verb GET. You can't add other methods.
Now you need to decide how the resource is named. You have a canonical name (your id), and an alias. One approach is to set up the controller for
GET /things/:id
so that :id can be either the canonical id or the alias. So you'd have
app.get('/resources/id', function (req, res) {
var id = req.params.id;
if (isAlias(id)) id = resolveAlias(id);
Thing.findById(id, null, function (err, thing) {
if (err) res.json(400, err)
if (thing === null) res.json(404, {"No such id": id})
res.json(thing)
});
});
You can also put in the alias as a query parameter, like you suggested.
I suspect the only other way might be to use a different url (somethng other than things) but I think this is disingenuous because you want to return the same representation whether or not you use the id or the alias. It should be the same controller, and you should be using GET, so I believe you need to go with the path parameter or query parameter.
This choice is independent of query rewriting, by the way.
I'm trying to get notifications in a CouchDB change poll as soon as pre-defined field is set or changed. I've already had a look at filters that can be used for filtering change events(db/_changes?filter=myfilter). However, I've not yet found a way to include this temporal information, because you can only get the current version of the document in this filter functions.
Is there any possibility to create such a filter?
If it does not work, I could export my field to a separate database and the only poll for changes in that db, but I'd prefer to keep together my data for obvious reasons.
Thanks in advance!
You are correct: filters and _changes feeds can only see snapshots of a document. What you need is a function which can see the old document and the new document and act correctly. But that is unavailable in _filters and _changes.
Obviously your client code knows if it updates that field. You might update your client code however there is a better solution.
Update functions can access both documents. I suggest you make an _update
function which notices the field change and flags that in the document. Next you
have a simple filter checking for that flag. The best part is, you can use a
rewrite function to make the HTTP API exactly the same as before.
1. Create an update function to flag interesting updates
Your _design/myapp would be {"updates", "smart_updater": "(see below)"}.
Update functions are very flexible (see my recent update handlers
walkthrough). However we only want to mimic the normal HTTP/JSON API.
Your updates.smart_updater field would look like this:
function (doc, req) {
var INTERESTING = 'dollars'; // Set me to the interesting field.
var newDoc = JSON.parse(req.body);
if(newDoc.hasOwnProperty(INTERESTING)) {
// dollars was set (which includes 0, false, null, undefined
// values. You might test for newDoc[INTERESTING] if those
// values should not trigger this code.
if((doc === null) || (doc[INTERESTING] !== newDoc[INTERESTING])) {
// The field changed or created!
newDoc.i_was_changed = true;
}
}
if(!newDoc._id) {
// A UUID generator would be better here.
newDoc._id = req.id || Math.random().toString();
}
// Return the same JSON the vanilla Couch API does.
return [newDoc, {json: {'id': newDoc._id}}];
}
Now you can PUT or POST to /db/_design/myapp/_update/[doc_id] and it will feel
just like the normal API except if you update the dollars field, it will add
an additional flag, i_was_changed. That is how you will find this change
later.
2. Filter for documents with the changed field
This is very straightforward:
function(doc, req) {
return doc.i_was_changed;
}
Now you can query the _changes feed with a ?filter= parameter. (Replication
also supports this filter, so you could pull to your local system all documents
which most recently changed/created the field.
That is the basic idea. The remaining steps will make your life easier if you
already have lots of client code and do not want to change the URLs.
3. Use rewriting to keep the HTTP API the same
This is available in CouchDB 0.11, and the best resource is Jan's blog post,
nice URLs in CouchDB.
Briefly, you want a vhost which sends all traffic to your rewriter (which itself
is a flexible "bouncer" to all design doc functionality based on the URL).
curl -X PUT http://example.com:5984/_config/vhosts/example.com \
-d '"/db/_design/myapp/_rewrite"'
Then you want a rewrites field in your design doc, something like (not
tested)
[
{
"comment": "Updates should go through the update function",
"method": "PUT",
"from": "db/*",
"to" : "db/_design/myapp/_update/*"
},
{
"comment": "Creates should go through the update function",
"method": "POST",
"from": "db/*",
"to" : "db/_design/myapp/_update/*"
},
{
"comment": "Everything else is just like normal",
"from": "*",
"to" : "../../../*"
}
]
(Once again, I got this code from examples and existing code I have laying
around but it's not 100% debugged. However I think it makes the idea very clear.
Also remember this step is optional however the advantage is, you never have to
change your client code.)