Meteor REST APIs with JsonRoutes

Do you mind showing me the complete code you wrote with fine-rest?

This code is at the top of the file:

import { JsonRoutes } from 'fine-rest/json-routes';

JsonRoutes.setResponseHeaders({
  "content-type": "application/json; charset=utf-8"
});

JsonRoutes.setResponseHeaders({
  "Cache-Control": "no-store",
  "Pragma": "no-cache",
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, PUT, POST, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With"
});

JsonRoutes.Middleware.use('api/packages/v1/', (req, res, next) => {
  const authUserId = req.userId;
  if (!authUserId) {
    JsonRoutes.sendResult(res, {
      code: 401,
      data: {
        result: "ERROR"
      }
    });
  }

  next();
});

And this code is at the bottom of the same file:

JsonRoutes.add('POST', 'api/packages/v1/test', (req, res) => {
  ...
}

UPDATE: I get an error if I call next() after the sendResult() above:

Exception in callback of async function: Error: Can't set headers after they are sent.

If I make the call to http://localhost:3000/api/packages/v1/test with the code above, the route api/packages/v1/test will always run, even if sendResult() in the middle ware is called. I don’t want this, I want the middleware to kick them out of the process and return a 401 to the client if they’re not auth’d.

If I don’t call next() if there is an error, and just call sendResult, then the ‘api/packages/v1/test’ is never called, which is the result I want – except the statusCode is incorrect.

JsonRoutes.Middleware.use('api/packages/v1/', (req, res, next) => {
  const authUserId = req.userId;
  if (!authUserId) {
    JsonRoutes.sendResult(res, {
      code: 401,
      data: {
        result: "ERROR"
      }
    });
  }
  else {
    next();
  }
});

The above works, but the statusCode is not present, I get this result:

{
    "result": "ERROR"
}
1 Like

You’re right it seems that next is called anyway (which makes sense actually, maybe you want the next middleware to have a shot). Returning or using a condition like you did avoids the headers error.

I actually read a bit in the connect docs now (on which JsonRoutes is based) and the flow should be this:

If you want to stop the flow you can return, but best practice is to call next with a param before (this means you are also throwing an error)

  JsonRoutes.Middleware.use(function(req, res, next) {

    if(req.query.error) {
      JsonRoutes.sendResult(res, {
        code: 401,
        data: {
          result: "ERROR"
        }
      });
      next(new Error("unauthorized"));
      return;
    }
    next();
  });

If you want to handle that error you can add a middleware with 4 parameters instead of three:

  JsonRoutes.Middleware.use(function(err, req, res, next) {
    console.log("Error found", err)
  });

if next(new Error("anuthorized")) is called, connect will skip any subsequent middleware which doesn’t have 4 params and just run those middlewares with 4 params to handle the error.

I don’t have any other code, i just import meteor and fine-rest. I’ve added headers and it works too.

But can you add the error middleware and see if something changes? It might catch an error you’re not aware of now.

This is the test repo if it helps: https://github.com/afruth/test-json-routes and this is the actual code https://github.com/afruth/test-json-routes/blob/master/server/main.js

1 Like

The lastest test results:

To provide a little more context to you, and to let you know I’m using a little more “real-world” testing, I started calling this web service from a Meteor HTTP.call(‘POST’, …) server side (aka the “client” in this case).


JsonRoutes.Middleware.use((req, res, next) => {
  const token = true;
  console.log('inside middleware');
  if (token) {
    console.log('inside error condition');
    JsonRoutes.sendResult(res, {
      code: 401,
      data: {
        result: "ERROR"
      }
    });
  }
  else {
    next();
  }
});

JsonRoutes.add('POST', 'test-route', (req, res) => {
  console.log('inside route');
  JsonRoutes.sendResult(res, {
    data: {
      url: 'http://google.com',
      result: "OK"
    }
  });
});

Makes it to the ‘inside error condition’, hangs, and it never makes back to the Meteor server (“client”).


JsonRoutes.Middleware.use((req, res, next) => {
  const token = true;
  console.log('inside middleware');
  if (token) {
    console.log('inside error condition');
    JsonRoutes.sendResult(res, {
      code: 401,
      data: {
        result: "ERROR"
      }
    });
    next(new Error("unauthorized"));
    return;
  }
  else {
    next();
  }
});

...

Here I get to the ‘inside error condition’ console log and then the Meteor error is thrown of course, Error: unauthorized, and it never makes back to the Meteor server (“client”).


JsonRoutes.Middleware.use((req, res, next) => {
  const token = true;
  console.log('inside middleware');
  if (token) {
    console.log('inside error condition');
    JsonRoutes.sendResult(res, {
      code: 401,
      data: {
        result: "ERROR"
      }
    });
    next();
    return;
  }
  else {
    next();
  }
});

...

Here, again it gets to the sendResults, but then throws at next(), Exception in callback of async function: Error: Can't set headers after they are sent., and it never makes back to the Meteor server (“client”).


JsonRoutes.Middleware.use((req, res, next) => {
  const token = true;
  console.log('inside middleware');
  if (token) {
    console.log('inside error condition');
    JsonRoutes.sendResult(res, {
      code: 401,
      data: {
        result: "ERROR"
      }
    });
    return;
  }
  else {
    next();
  }
});

...

I remove the next() and just use return, hangs, and it never makes back to the Meteor server (“client”).


Now here it gets interesting…

JsonRoutes.Middleware.use((req, res, next) => {
  const token = true;
  console.log('inside middleware');
  if (token) {
    console.log('inside error condition');
    JsonRoutes.sendResult(res, {
      data: {
        result: "ERROR"
      }
    });
  }
  else {
    next();
  }
});

...

I remove the code, and bam, it responds and makes it back to the Meteor server (“client”); this is a dump (JSON.stringify(result)) of the response from the Web API to the Meteor server (“client”):

result: {"statusCode":200,"content":"{\n  \"result\": \"ERROR\"\n}","headers":{"content-type":"application/json","vary":"Accept-Encoding","date":"Thu, 13 Jul 2017 06:00:58 GMT","connection":"close","transfer-encoding":"chunked"},"data":{"result":"ERROR"}}

My take away, I remove the code property from the sendResult options object and I the lib “MECHANICS” work as expected when it comes to the middleware and response – yet the result is flawed because I cannot inject the real statusCode (aka code).

I don’t know if there’s a bug in the JsonRoutes lib, the JsonRoutes code I made that accepts the requests, or if I’m not calling the darn thing right from Meteor server side (i.e., my HTTP.call() doesn’t have something it needs).

Can you try to:

  1. Instead of returning from your middleware, pss a Meteor.Error to next
  2. Use the error handling middleware package (which is designed to sanitize annd return meteor errors)

I’m unfortunately less familiar with json routes now that I’ve been using connect for some time. And with connect, I never return from my middleware, they always either modify the request object and call next() or call next() with an error and I have an error handling middleware (the 4 parameter one I had mentioned in one of my earlier replies) that handles sanitizing and properly returning a response.

1 Like

@afruth, I did it your way, and this is what I found:

JsonRoutes.Middleware.use((req, res, next) => {
  const token = true;
  console.log('inside middleware');
  if (token) {
    console.log('inside error condition');
    JsonRoutes.sendResult(res, {
      code: 401,
      data: {
        result: "ERROR"
      }
    });
    next(new Error("unauthorized"));
    return;
  }
  else {
    next();
  }
});

JsonRoutes.Middleware.use(function(err, req, res, next) {
   console.log("Error found", err)
 });
JsonRoutes.add('POST', 'test-route', (req, res) => {
  console.log('inside route');
  JsonRoutes.sendResult(res, {
    code: 200,
    data: {
      url: 'http://google.com',
      result: "OK"
    }
  });
});

With the code above, if I call out to it from server side Meteor like this:

  async test(userId, url, token) {
    let result = null;
      result = await HTTP.call('POST', 'http://localhost:3000/test-route',
        {
          data: { userId: userId },
          headers: { 'content-type': 'application/x-www-form-urlencoded', 'authorization': `Bearer ${token}`, 'cache-control': 'no-cache' }
        }
      );
      console.log(`inside server side method call, result: ${JSON.stringify(result)}`);
      return result;
  },

The server side method just hangs… same as before.


If I remove the code option inside the sendResults function, as in my last post:

Removed code:

JsonRoutes.Middleware.use((req, res, next) => {
  const token = true;
  console.log('inside middleware');
  if (token) {
    console.log('inside error condition');
     // REMOVED "code" here:
    JsonRoutes.sendResult(res, {
      data: {
        result: "ERROR"
      }
    });
    next(new Error("unauthorized"));
    return;
  }
  else {
    next();
  }
});

...

With the code removed, I get the response back in my Meteor Method back on the server:

result: {"statusCode":200,"content":"{\n  \"result\": \"ERROR\"\n}","headers":{"content-type":"application/json","vary":"Accept-Encoding","date":"Thu, 13 Jul 2017 17:45:11 GMT","connection":"close","transfer-encoding":"chunked"},"data":{"result":"ERROR"}}

So either way, throwing the exception and catching the exception in another middleware function OR just doing sendResult without throwing, with the code property inside the sendResults function, the response hangs, without the code it returns.

1 Like

instead, try this:

next(new Meteor.Error('unauthorized'));

And use the error handling middleware from simple:rest

Edit: I don’t know what came over me, you do need to pass the error to next, not throw. It’s one those times where you unconsciously do one thing and think you’re doing something else :slight_smile:

1 Like

Can you put a repo with your actual code? Might help figuring it out.

2 Likes

Thanks @afruth & @serkandurusoy, I built a example repo for reference: GitHub - aadamsx/meteor-web-app-test: fine-rest example in Meteor

/web-api is the Meteor Web API using JsonRoutes.

/web-api-user is the Meteor client to the Web API, with a front end and a server side that makes the call to the Web API.

To use: If you click the ‘Click Me’ button it will make a server side Meteor Method call, which in turn calls to the Web API.

You’ll need to run both Web API and Web API User app at the same time with this command: npm start web-app-test. The ports are set up so the Meteor Method will be able to find the Web API.

I left the API exactly how @afruth coded his, and in this case the Meteor Method call will just hang.

Thanks for the help!

1 Like

You should also commit your .meteor directory for both apps so we don’t have to guess the packages used :slight_smile:

Still, do you get a correct answer using GET and the browser?

I changed my code to POST and still works, using Postman to check.

Can you test these two things: does it work in browser with GET? Does it work in Postman with POST (or even curl if you have it curl --request POST http://localhost:3000/test-route?error=true?

You are adding many variables in your test case (two meteor apps, http.call etc) and you need to simplify a bit to see where the issue is.

Coming back to your code above:

  • you don’t need to await HTTP.call server-side it’s a sync method unless you give it a callback.
  • maybe try to try{}catch(){} the HTTP.call and see if you get any error.

Done.

Sorry, I’m not following here. My Web API is meant to be called from a Meteor Server HTTP.call() method. So I build a very basic one to test it out. I built the example repo so we could all get on the same page is all. :slight_smile:

I see the status code in Postman too, but examining the return to the HTTP.call(), the entire response object, I do not see it, anything but a statusCode of 200, what did you see running the example app I posted?

I was trying to make it as simple as possible, but still use the tech I’m going to use in the end. I thought the HTTP.call() was very basic and clean.

If you don’t make it async, the method will just return from the HTTP call without waiting for a response, and therefore my front end code will not have the correct values it needs. I tried to do this sync to start – doesn’t work when your UI code depends on a response value from the Web API.

In my original code, not the repo example I posted, I DO have everything in a try/catch block. I removed it to make the example more simple and remove as many variables as possible, yet still keep some semblance of the real-world application I ultimately will make. And with a try/catch block, there is no difference on in the Meteor Server side Method – there is no change to how the call reacts to the Web API or the response from my testing.

Did you run the code example, what did you find, does it hang for you?

The HTTP.call returns something when the call is ok or throws and error when it gets an error. That’s why it returned. Otherwise it would just wait until it gets a response.

I’ll try again your apps now with the meteor directories. Tried before but stopped after too many errors related to missing packages :slight_smile:

1 Like

Right, but it doesn’t return anything when this sendResults contains a code property:

It just hangs.

If I remove the code property, you’re right, it will return ok or error. I can’t set the 401 for the HTTP.call().

Now in Postman, it does find the code when its set as your gif earlier showed. Although I don’t know how to see the raw response in Postman – in other words, I coudn’t find where in the response the code was being set in Postman.

Sorry about that, don’t know how .meteor got in my .gitignore file. :smiley:

I’ve tried it with your latest code and i get a server side error:

Error found [TypeError: Cannot read property 'findOne' of undefined]

This is because Meteor.users is undefined, which probably is because you don’t have accounts-base in the package (JsonRoutes has some code pertaining to Meteor.users to get tokens and stuff).

Adding the Accounts package breaks the mongo connectivity (can’t connect anymore to the Mongo server, which is weird).

Is this your actual code repo you’re using to test? Or is something you put together for us? In this case can you please try to install it locally and see if you have the same issues as me?

If you see the code in Postman and the response body, for both calls (with or without error) then it’s working correctly (the JsonRoutes part) and the issue is in the receiving code (HTTP.call part).

When you are saying it hangs, are you referring that the server-side console.log(“Error found:”) is not run? Or that your client (well server on client app) doesn’t work as expected?

The fact that you are having issues only with errors make me pretty sure you need to catch them when HTTP.call-ing.

1 Like

So sorry, I was using my local version, I switch over to use the test version and indeed got the error. I added the accounts package and can now show the “hang” when called with HTTP.call(). The repo is now updated.

Also, in the web-api middleware, to show the error case without a URL param, I changed the “if” statement a bit. Otherwise everything is the same.


Also, so we are in sync, if I call that same route with Postman, it does not hang:

So maybe the issue IS with the HTTP.call()?


The hang on error does indeed exist. Do I need to do another sendResult() inside the second middleware function perhaps?

Ok so here’s what’s wrong :slight_smile: Simple Meteor magic

You are throwing another error in your catch clause but you don’t do nothing with it. You need to get it on client and show or something.

If you console.log the error in your catch clause you’ll see the following error:

{ [Error: failed [401] {   "result": "ERROR" }]
I20170714-17:50:08.306(3)?   response: 
I20170714-17:50:08.306(3)?    { statusCode: 401,
I20170714-17:50:08.307(3)?      content: '{\n  "result": "ERROR"\n}',
I20170714-17:50:08.307(3)?      headers: 
I20170714-17:50:08.308(3)?       { 'content-type': 'application/json',
I20170714-17:50:08.308(3)?         vary: 'Accept-Encoding',
I20170714-17:50:08.309(3)?         date: 'Fri, 14 Jul 2017 14:50:08 GMT',
I20170714-17:50:08.309(3)?         connection: 'close',
I20170714-17:50:08.309(3)?         'transfer-encoding': 'chunked' },
I20170714-17:50:08.310(3)?      data: { result: 'ERROR' } } }

Which is what should happen. So it doesn’t hang, you’re not treating the error :slight_smile: Also i’ve removed async and await, HTTP.call doesn’t need it server side.

Just to describe the flow in case someone needs it in the future:

If you use HTTP.call server-side, a sync function, it returns the result on succesful HTTP code or throws an Error if it failed with an unsuccessful HTTP code (400+).
The Error should be caught with a try / catch block. If you are in a meteor method and throw the error further, the Error goes to the client as the first param of the callback function. Server-side you can do stuff with the error before throwing it again, like console.logging it.

Hope that solves your issue man.

1 Like

Wow! Thank you!

I’m just a little confused, so … your saying the response IS getting back to the client, in this case the Meteor Method with the HTTP.call(), but since I have a try/catch around it, it’s going to the catch, and i’m throwing again in the catch and not picking it up in the Browswer client? Is this response from the Web API handled as an Error in the HTTP.call()?

I also looked at the client side now and indeed you were getting the error client side and you were throwing it again. Just log the error on client instead of throwing it yet again and see what happens.

And by the way, in the catch block you get an HTTP error not a Meteor one. You shouldn’t just re-throw it, maybe try something like throw new Meteor.Error(err.response.statusCode, err.response.data.result) to have a proper Meteor Error client side

Why is the response handled like an error anyhow? It’s just a JSON object.

Because that’s how HTTP.call works :smiley:

From the docs:

On the server, this function can be run either synchronously or asynchronously. If the callback is omitted, it runs synchronously and the results are returned once the request completes successfully. If the request was not successful, an error is thrown.

What property within the response generates the error? Is it the statusCode? If we return a statusCode of 200, then no error on the HTTP.call(), but a 401 will generate an exception error?