Giter VIP home page Giter VIP logo

api-query-params's Introduction

api-query-params

NPM version Build Status Coveralls Status Dependency Status Downloads

Convert query parameters from API urls to MongoDB queries (advanced querying, filtering, sorting, …)

Features

  • Powerful. Supports most of MongoDB operators ($in, $regexp, …) and features (nested objects, projection, type casting, …)
  • Custom. Allows customization of keys (ie, fields vs select) and options
  • Agnostic. Works with any web frameworks (Express, Koa, …) and/or MongoDB libraries (mongoose, mongoskin, …)
  • Simple. ~200 LOCs, dependency-free ES6 code
  • Fully tested. 100% code coverage

Install

npm i --save api-query-params

Usage

API

aqp(queryString, [opts])

Converts queryString into a MongoDB query object

Arguments
  • queryString: query string part of the requested API URL (ie, firstName=John&limit=10). Works with already parsed object too (ie, {status: 'success'}) [required]
  • opts: object for advanced options (See below) [optional]
Returns

The resulting object contains the following properties:

  • filter which contains the query criteria
  • projection which contains the query projection
  • sort, skip, limit which contains the cursor modifiers
  • population which contains the query population (mongoose feature only)

Example

import aqp from 'api-query-params';

const query = aqp(
  'status=sent&timestamp>2016-01-01&author.firstName=/john/i&limit=100&skip=50&sort=-timestamp&populate=logs&fields=id,logs.ip'
);
//  {
//    filter: {
//      status: 'sent',
//      timestamp: { $gt: Fri Jan 01 2016 01:00:00 GMT+0100 (CET) },
//      'author.firstName': /john/i
//    },
//    sort: { timestamp: -1 },
//    skip: 50,
//    limit: 100,
//    projection: { id: 1 },
//    population: [ { path: 'logs', select: { ip: 1 } } ]
//  }

Example with Express and mongoose

import express from 'express';
import aqp from 'api-query-params';
import User from './models/User';

const app = express();

app.get('/users', (req, res, next) => {
  const { filter, skip, limit, sort, projection, population } = aqp(req.query);
  User.find(filter)
    .skip(skip)
    .limit(limit)
    .sort(sort)
    .select(projection)
    .populate(population)
    .exec((err, users) => {
      if (err) {
        return next(err);
      }

      res.send(users);
    });
});

That's it. Your /users endpoint can now query, filter, sort your User mongoose model and more.

Supported features

Filtering operators

MongoDB URI Example Result
$eq key=val type=public {filter: {type: 'public'}}
$gt key>val count>5 {filter: {count: {$gt: 5}}}
$gte key>=val rating>=9.5 {filter: {rating: {$gte: 9.5}}}
$lt key<val createdAt<2016-01-01 {filter: {createdAt: {$lt: Fri Jan 01 2016 01:00:00 GMT+0100 (CET)}}}
$lte key<=val score<=-5 {filter: {score: {$lte: -5}}}
$ne key!=val status!=success {filter: {status: {$ne: 'success'}}}
$in key=val1,val2 country=GB,US {filter: {country: {$in: ['GB', 'US']}}}
$nin key!=val1,val2 lang!=fr,en {filter: {lang: {$nin: ['fr', 'en']}}}
$exists key phone {filter: {phone: {$exists: true}}}
$exists !key !email {filter: {email: {$exists: false}}}
$regex key=/value/<opts> email=/@gmail\.com$/i {filter: {email: /@gmail.com$/i}}
$regex key!=/value/<opts> phone!=/^06/ {filter: {phone: { $not: /^06/}}}

For more advanced usage ($or, $type, $elemMatch, etc.), pass any MongoDB query filter object as JSON string in the filter query parameter, ie:

aqp('filter={"$or":[{"key1":"value1"},{"key2":"value2"}]}');
//  {
//    filter: {
//      $or: [
//        { key1: 'value1' },
//        { key2: 'value2' }
//      ]
//    },
//  }

Skip / Limit operators

  • Useful to limit the number of records returned.
  • Default operator keys are skip and limit.
aqp('skip=5&limit=10');
//  {
//    skip: 5,
//    limit: 10
//  }

Projection operator

  • Useful to limit fields to return in each records.
  • Default operator key is fields.
  • It accepts a comma-separated list of fields. Default behavior is to specify fields to return. Use - prefixes to return all fields except some specific fields.
  • Due to a MongoDB limitation, you cannot combine inclusion and exclusion semantics in a single projection with the exception of the _id field.
  • It also accepts JSON string to use more powerful projection operators ($, $elemMatch or $slice)
aqp('fields=id,url');
//  {
//    projection: { id: 1, url: 1}
//  }
aqp('fields=-_id,-email');
//  {
//    projection: { _id: 0, email: 0 }
//  }
aqp('fields={"comments":{"$slice":[20,10]}}');
//  {
//    projection: { comments: { $slice: [ 20, 10 ] } }
//  }

Sort operator

  • Useful to sort returned records.
  • Default operator key is sort.
  • It accepts a comma-separated list of fields. Default behavior is to sort in ascending order. Use - prefixes to sort in descending order.
aqp('sort=-points,createdAt');
//  {
//    sort: { points: -1, createdAt: 1 }
//  }

Population operator

  • Useful to populate (reference documents in other collections) returned records. This is a mongoose-only feature.
  • Default operator key is populate.
  • It accepts a comma-separated list of fields.
  • It extracts projection on populated documents from the projection object.
aqp('populate=a,b&fields=foo,bar,a.baz');
// {
//    population: [ { path: 'a', select: { baz: 1 } } ],
//    projection: { foo: 1, bar: 1 },
//  }

Keys with multiple values

Any operators which process a list of fields ($in, $nin, sort and projection) can accept a comma-separated string or multiple pairs of key/value:

  • country=GB,US is equivalent to country=GB&country=US
  • sort=-createdAt,lastName is equivalent to sort=-createdAt&sort=lastName

Embedded documents using . notation

Any operators can be applied on deep properties using . notation:

aqp('followers[0].id=123&sort=-metadata.created_at');
//  {
//    filter: {
//      'followers[0].id': 123,
//    },
//    sort: { 'metadata.created_at': -1 }
//  }

Automatic type casting

The following types are automatically casted: Number, RegExp, Date and Boolean. null string is also casted:

aqp('date=2016-01-01&boolean=true&integer=10&regexp=/foobar/i&null=null');
// {
//   filter: {
//     date: Fri Jan 01 2016 01:00:00 GMT+0100 (CET),
//     boolean: true,
//     integer: 10,
//     regexp: /foobar/i,
//     null: null
//   }
// }

If you need to disable or force type casting, you can wrap the values with string(), date() built-in casters or by specifying your own custom functions (See below):

aqp('key1=string(10)&key2=date(2016)&key3=string(null)');
// {
//   filter: {
//     key1: '10',
//     key2: Fri Jan 01 2016 01:00:00 GMT+0100 (CET),
//     key3: 'null'
//   }
// }

Available options (opts)

Customize operator keys

The following options are useful to change the operator default keys:

  • skipKey: custom skip operator key (default is skip)
  • limitKey: custom limit operator key (default is limit)
  • projectionKey: custom projection operator key (default is fields)
  • sortKey: custom sort operator key (default is sort)
  • filterKey: custom filter operator key (default is filter)
  • populationKey: custom populate operator key (default is populate)
aqp('organizationId=123&offset=10&max=125', {
  limitKey: 'max',
  skipKey: 'offset',
});
// {
//   filter: {
//     organizationId: 123,
//   },
//   skip: 10,
//   limit: 125
// }

Blacklist / Whitelist

The following options are useful to specify which keys to use in the filter object. (ie, avoid that authentication parameter like apiKey ends up in a mongoDB query). All operator keys are (sort, limit, etc.) already ignored.

  • blacklist: filter on all keys except the ones specified
  • whitelist: filter only on the keys specified
aqp('id=e9117e5c-c405-489b-9c12-d9f398c7a112&apiKey=foobar', {
  blacklist: ['apiKey'],
});
// {
//   filter: {
//     id: 'e9117e5c-c405-489b-9c12-d9f398c7a112',
//   }
// }

Add custom casting functions

You can specify you own casting functions to apply to query parameter values, either by explicitly wrapping the value in URL with your custom function name (See example below) or by implictly mapping a key to a function (See Specify casting per param keys below). Note that you can also override built-in casting functions: boolean, date ,null ,number ,regex and string.

  • casters: object to specify custom casters, key is the caster name, and value is a function which is passed the query parameter value as parameter.
aqp('key1=lowercase(VALUE)&key2=int(10.5)&key3=true', {
  casters: {
    lowercase: val => val.toLowerCase(),
    int: val => parseInt(val, 10),
    boolean: val => (val === 'true' ? '1' : '0'),
  },
});
// {
//   filter: {
//     key1: 'value',
//     key2: 10,
//     key3: '1',
//   }
// }

Specify casting per param keys

You can specify how query parameter values are casted by passing an object.

  • castParams: object which map keys to casters (built-in or custom ones using the casters option).
aqp('key1=VALUE&key2=10.5&key3=20&key4=foo', {
  casters: {
    lowercase: val => val.toLowerCase(),
    int: val => parseInt(val, 10),
  },
  castParams: {
    key1: 'lowercase',
    key2: 'int',
    key3: 'string',
    key4: 'unknown',
  },
});
// {
//   filter: {
//     key1: 'value',
//     key2: 10,
//     key3: '20',
//     key4: 'foo',
//   }
// }

License

MIT © Loris Guignard

api-query-params's People

Contributors

bolzon avatar coox avatar damienmarchandfr avatar daybr3ak avatar dependabot[bot] avatar f5hajnal avatar greenkeeperio-bot avatar jaysalvat avatar loris avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

api-query-params's Issues

paginate?

Can you use this for pagination or how would you use this together with pagination like for example how would you paginate would you need to use the page number before like so

http://localhost:3000/api/tst/users/1/?_id=5d74d73ad8ba0e04f80ee346
the number 1 being the page number
or after the query parameters although doing that after causes an error
I'm a newbie I apologize for this question

whitelist does not work with '>'

const aqp = require('api-query-params').default; 
const test = aqp('timestamp>2016-01-01');
 console.log(test);
 const test2 = aqp('timestamp>2016-01-01',{
   whitelist : ['timestamp']
 });
 console.log(test2);

the code above gives :

{ filter: { timestamp: { '$gt': 2016-01-01T00:00:00.000Z } } }
{ filter: {} }
$ node -v
v6.5.0

How to prevent nested populating?

I have the following Model:

const userSchema = new mongoose.Schema({
   name: String,
   by: { type: mongoose.Schema.ObjectId, ref: 'User' }
})
const UserModel = mongoose.model('User', userSchema, 'users')

and I am using api-query-params in one of my controllers:

const aqp = require('api-query-params')
app.get('/users', (req, res, next) => {
   const docs = await UserModel.find(aqp.filter).populate(aqp.population)
   res.status(200).json({  data: docs })
})

Let's say there's a user called A who created himself somehow.
I am requesting: http://localhost:4000/users?populate=by and I am getting the response as follows:

{ # 200 OK
   data: [
      {
          name: A,
          by: {
              name: A,
              by: 62e7960ebc9aa651523bf5b2 // Object Id
          }
      }
   ]
}

I am OK with this response, however, the problem comes when someone requests http://localhost:4000/users?populate=by.by.by.by

the response becomes:

{
    # 200 OK
    data: [{
        name: A,
        by: {
            name: A,
            by: {
                name: A,
                by: {
                    name: A,
                    by: 62e7960ebc9aa651523bf5b2 // Object ID
                }
            }
        }
    }]
}

Is there an option where I can only allow one level of populating in aqp (api-query-params)?

Provide as commonjs module

Hi, would be nice if you could transpile it with Babel so require and import can work in the same way. The import syntax is not supported in nodejs. Thanks.

Document the usage of populate in the case of multiple nested fields to populate

It would be nice to have a few lines of documentation showing that you should use spaces when you want to populate multiple nested fields.

When you have a request that looks like:
/orders/${id}?populate=quotation.customer,quotation.quotlines,movements

you end up with a populate object that looks like:
population [ { path: 'quotation', populate: { path: 'quotlines' } }, { path: 'movements' } ]
Instead of :
population [ { path: 'quotation', populate: [{ path: 'quotlines' }, { path: 'customer' }] }, { path: 'movements' } ]

The solution to that is that your request should be made using spaces for consecutive fields from the same key, like so:
/orders/${id}?populate=quotation.customer quotlines,movements

That solution was provided by a veteran user from my team, but it would be nice for it to be documented in the readMe in the populate section.

Strange string to number parsing

Hey, I have had a strange bug with this, I dont know why but if you input this random id to the querystring like this:

let { filter} = aqp({ uid: '115524599024168443300' }

The filter gets this as output:

{ uid: 115524599024168440000 }

It somehow changes the last threes for zeros. Maybe its some type of overflow? I have solved it by setting that particular castParam to string, but I suppose you wanted to know it.

Bug with !=

We are having an issues with the != operator. When we try to query where _id!=64514704b973cc48c724d563 instead of resolving this as $ne it instead is turning into $not which happens at this line

The reason this triggers is becasue we have a caster for this field:

import { ObjectId } from 'mongodb';
const aqpOptions = {
  casters: {
    mongoId: (val) => new ObjectId(val),
  },
  castParams: {
    _id: 'mongoId',
  },
};

What I would like to know is when is the $not operator intended to be added? In the documentation and examples I only see it next to regex values. Are there other cases where $not should be added as well, or would it be safe to also check value instanceof RegExp before using that condition?

Add option to Blacklist fields in populate

  • I have a "Users" collection with a user's password stored it "password" field.

  • I have another collection "Articles" with the "_id" of the "User" collection as the foreign key to "user" field in "Articles" collection

  • I can then populate the "user" field in "Articles" collection and get the password of the user by passing in "user.password"

This is a possible security flaw. It'd be great if you can provide a mechanism to overcome this.

Cannot use regex in filter with the $or operator

Hey there, I would like to use regex with the $or operator.

aqp('filter={"$or":[{"name_en":/^value/},{"name_fr":/^value/}]}'); // string comes directly from the query string

Unfortunately this produces the following error:

Invalid JSON string: {"$or":[{"name_en":/^value/},{"name_fr":/^value/}]}

Regex is correctly parsed when used on a single field, but I need to use it on multiple using conditions. Is there a workaround for this?

Many thanks

Incorrect type declarations/exports on latest version (5.3.1)

Hello,

I am not able to import the library as per the documentation. When doing it like so:

import aqp from 'api-query-params'

I get the following error: TypeError: (0 , api_query_params_1.default) is not a function. I'm attempting to call the function as in the docs' example yet again:

const { filter, skip, limit, sort, projection, population } = aqp(query)

When doing an old-fashioned require statement though, (e.g. const aqp = require('api-query-params') ) behavior is as expected and I'm not getting errors when attempting to call the function.

I would like to use the type definitions if possible to avoid mixing all my other import statements with this require one.
By the way, doing this:

import { AqpQuery } from 'api-query-params' for that exported type works well. it is the function the one I just can't import with the help of TS.

Any guidance would be much appreciated. Thank you

+ in the query parameter is stripped

I am trying to send a E.164 formatted phone number in query string.

Sample HTTP request

GET /api/nonprofits/?members.phone=%2B919831042144 HTTP/1.1

After parsing with api-query-params, the + in the param is stripped and I get this as a log for filter object

{ 'members.phone': 919831042144 }

It seems to be auto casting it to a number and therefore removing the + from the value.

Should values starting with + be considered numbers or should the check for numbers be made stricter? I think I can specify casters per key to override this but just wanted to understand why it is done this way

Special character's not matching

I am passing the special characters but it replaced as special characters.
Exmaple:

const aqp = require('api-query-params');
const q = {mobile_no : "+919876543210"};
const result  = aqp(q);
console.log(result);

Current Output:
{ filter: { mobile_no: 919876543210 } }

Expected Output:

{ filter: { mobile_no: +919876543210 } }

White list not work on advanced filter object

import * as aqp from 'api-query-params';

const query = aqp('filter={"$or":[{"key1":"value1"},{"key2":"value2"}]}', {
    whitelist: ['key1']
});
console.log(JSON.stringify(query, null, 2));

Will print out:

{
  "filter": {
    "$or": [
      {
        "key1": "value1"
      },
      {
        "key2": "value2"
      }
    ]
  }
}

Expected result:

{
  "filter": {
    "$or": [
      {
        "key1": "value1"
      }
    ]
  }
}

Regex filter not working

Hi, So I found a gap in the docs where if we attempt to use regex, it will basically just to match to a string
image

The final 2 rows in the table here show that if you try to search with regex, the mongoose object returned is going to be a string matcher and not a regex matcher.

Correct notation for the EQUALS would be {filter: {email : {$regex: /blahblah/i}}} or in the NOT case {filter: {email : { $not: {$regex: /blahblah/i}}}}

I have confirmed that this is what the code returns and when you attempt to search via regex, it doesnt work in the expected way. I copied the code locally and made these changes in my own way to serve this purpose. There was probably a time where this worked, perhaps with updated over time, this has now become defunct.

Doc

Hey

This is an awesome package. But the doc isn't clear. This will be better to have some real life axios or more example api request params usage

Not use 'fields' works - aqp('populate=a,b&fields=foo,bar,a.baz');

Thanks

how to use tarball from github?

I've forked this project to do some minor changes, which are specific for my project. I've recently updated a lot of project dependencies including api-query-params. With new version of npm I've run into problems with editing api-query-params code directly so I wanted some clean solution.

After forking api-query-params and adding fork url in package.json in my project the package installs fine, but the installed package looks a lot different - basically a copy of github repo. When i try to launch the whole app i just get Error: Cannot find module 'api-query-params'.

I figured out that the source needs to be built, but im totally lost what to do next to use my fork by npm in package.json to be installed as it should and to just work as before.

Can you please provide steps how to build package that can be used in package.json from github tarball?

Populate params

[EDIT]
I had actually some time and make
the PR #99 with a POC.

Hello Loris!
Thanks for sharing this great lib.

It would be great to have a populate params to handle relation populations.
https://mongoosejs.com/docs/populate.html

Ex. Passing ?populate=users to the query, would get:

  const { filter, skip, limit, sort, projection, population } = aqp(req.query);
  User.find(filter)
    .skip(skip)
    .limit(limit)
    .sort(sort)
    .select(projection)
    .populate(population) // <---- here
    .exec((err, users) => {
      if (err) {
        return next(err);
      }
 
      res.send(users);
    });

... would turn that

{
  "_id": "5ce158dd5e60d70972ff97dc",
  "name": "admins",
  "users": [
    "5cded210825b89040cb81185",
    "5cdebeb003db96001fabd040",
  ]
}

in that:

{
  "_id": "5ce158dd5e60d70972ff97dc",
  "name": "admins",
  "users": [
    { 
      "_id": "5cded210825b89040cb81185",
      "firstname": "brad",
      "lastname": "pitt"
    },
    {
      "_id": "5cdebeb003db96001fabd040",
      "firstname": "matt",
      "lastname": "damon"
    }
  ]
}

Since this lib is already handling projection, it could handle population projection:
Ex. ?populate=users&field=name,users.lastname

TL;DR;

?populate=a,b,c

{ 
  population: [ 'a', 'b', 'c' ];
}
?populate=a,b,c?fields=a.f1,a.f2,b.f3.c.f4

{
  population: [
    { path: 'a', select: [ 'f1', 'f2' ]},
    { path: 'b', select: [ 'f3' ]},
    { path: 'c', select: [ 'f4' ]}
  ]
}

What do you think ?

Paginate while still maintaining other query params?

Ok so the 1 in your URL (http://localhost:3000/api/tst/users/1) is not an URL query parameter but an URL path parameter, so you can use the library this way:

import express from 'express';
import aqp from 'api-query-params';
import User from './models/User';

const app = express();

app.get('/api/tst/users/:page', (req, res, next) => {
  const recordsPerPage = 10;
  const { filter, skip, limit, sort, projection, population } = aqp({ skip: req.params.page * recordsPerPage, ...req.query});
  ... // same as in the README.md example
});

In the example, I extract the page value from the req.params object, and build a virtual skip variable (which is page number * number of records per page, so if you're on a page 3, you will skip the first 30 records for instance), let me know if this is clear
@loris

Doing this does not work when I change the page number the same records still appear I'm using it like this http://localhost:3000/api/tst/users/1/?skip=1

since using it like this
http://localhost:3000/api/tst/users/1/

without the skip param gives me an empty array
when I go to the second page nothing changes
like this http://localhost:3000/api/tst/users/**2**/?skip=1
the same records still apear

Sorry sorry I've only just realized I have around 6 records and in your example you've used 10 records per page so THANKS it works as expected however how would I use this on the front end since I want to change the page number while still keeping the user's filters

like so how would I do this
http://localhost:3000/api/tst/users/3/?featured=true while changing the page number but still have the query params in place

Originally posted by @Chukstart in #101 (comment)

$in queries not generated correctly for ObjectIds types

When running a query such as api/strore?owner=5996e60a6097712268bcfcb5,599999990e58541d229ef318

generates the query:
{ owner: { $in: [ "5996e60a6097712268bcfcb5", "599999990e58541d229ef318" ] } }

It should generate the query as follows:
{ owner: { $in: [ ObjectId("5996e60a6097712268bcfcb5"), ObjectId("599999990e58541d229ef318") ] } }

I am passing the following to the method:

var agpOptions = {
casters: {
mongoId: val => mongo.ObjectId(val)
},
castParams: {
_id: 'mongoId',
owner: 'mongoId'
}
};

var parsedQuery = aqp(queryString, agpOptions);

Option to do custom coercions/type casting?

Firstly, thanks for a really useful library, does 99% of what I need to do.

I'm looking for a way to coerce an specific field in the filter result to a mongodb.ObjectId before I run the query against the db, is there currently a way to do this in the library?

So for example a way to do it manually at the moment would be:

'use strict';
var mongo = require('mongodb');
var aqp = require('api-query-params').default;

var queryString = '?ownderId:5829ba2b7a3a7a08307483a9';
var parsedQueryString = aqp(queryString);
parsedQueryString.ownderId = mongo.ObjectId(parsedQueryString.ownderId);
mongo.db.collection('items').findOne(parsedQueryString.filter, dataRetrieved);

In the above example it's fairly trivial, but it would be nice to have an extensible way of doing coercion before the query goes into the db. The way I see it there are a few options if this functionality doesn't already exist:

Custom functions

Allow setting up of custom "functions" in the query string before parsing, in a similar manner to:

aqp('key1=string(10)&key2=date(2016)&key3=string(null)');

For example

aqp.addFunction('mongoId', function(stringValue, keyName, fullQueryString){
    return mongo.ObjectId(stringValue);
});
aqp('?ownderId:mongoId(5829ba2b7a3a7a08307483a9)');

Key Name Lookup

Use the key name in the query string to do type casting

aqp.coerce(['ownderId'], function(stringValue, keyName, fullQueryString){
    return mongo.ObjectId(stringValue);
});
aqp('?ownderId:5829ba2b7a3a7a08307483a9');

Apologies if there is already a way to do this.

Sort and filter in population

Hi Loris,
I'm using your lib for a long time and it's amazing: thanks!

I found some limits in sorting and filtering in populated content.

If I've understand well, it's impossible to sort by populated fields:
?populate=a,b,c&sort=a.f1,b.f2,c.f1

or to filter by populated fields:
?populate=a,b,c&a.f1=value&b.f2=value2

Is it right?
Do you have any suggestion about this need?

At the moment I tried to use "advanced filter" like
filter={"$or":[{"key1":"value1"},{"key2":"value2"}]}
but it's not so comfortable.

Thanks for any help ✌️

Documentation

  • Features list (es6, dependency-free, operators, well-tested, etc.)
  • Full example usage with Express/Mongoose
  • Detailed usage (operators, options, etc.)

Ability to map filter operators

Create ability to customize filter keys. May be useful for other orm.
For example:
count>5
{filter: {count: {gt: 5}}}
Key gt without $

Querying for string with commas in value

Is there a built in way to handle querying for strings that include commas inside them? For example:

thing=a,b,c

In this case it will search the field thing for a value of a, b or c.

Instead, we need it to search the field thing for a value of a,b,c.

Just as a note, I've also attempted forcing a string for the whole value but that hasn't worked for me either:

thing=string(a,b,c)

Exclude key=""

Really nice lib!

The only thing I'm having trouble figure out is how to exclude "" values:

{
 id: 23423,
 key1 : "test",
 key2 : "" 
}

I tried &key2!="" with no effect.

Thanks!

order of query params resolves to different results

Short description
When passing a different order of query parameters, the output will be different. I assume, query params are sequence order insensitive. If you disagree about my assumption, this issue is meaningless to you.

Example

// variant 1: NE before EQ
const query = aqp('type!=10a&type=20b');

//  {
//    filter: {
//      type: '10a'
//    },
//  }
// variant 2: EQ before NE
const query = aqp('type=10a&type!=20b');

//  error: 'Cannot create property '$ne' on string '10a'

Code debugging
When you change the order of "equals" = and "not equals to" !=, the query parser will fail due to internal parser logic:
'Cannot create property '$ne' on string '10a''. Both variants are kinda wrong. So far, my debugging resolves to the method getFilter.

Variant 1: The first iteration will lead to an operator === '$ne' and assign the value like result[key][op] = value. The second iteration will lead to operator === '$eq' and will reassign like result[key] = value`. Therefore the later iteration will override the first one. Is this intentional?

Variant 2: The first iteration will lead to an operator === '$eq' and it will assign the value like result[key] = value. The second iteration, will lead to operator === '$ne' and therefore reaches the default/else case result[key][op] = value. The last statement will create the previously announced error message, because result[key] is already a sting value from the first iteration.

Upcoming
A quick solution from our team was to use always the specific operator and create always the operator specific filter:

...
} else if (op === '$eq') {
      result[key][op] = value;
} ...

Please give feedback about this topic and provide a roadmap/fix to satisfy this requirement.

How can I populate a property in a sub-document ?

Can i get populate query parameter like this { path: 'array.objectProperty' } but when i send populate param ?populate=array.objectProperty. The populate that I received was multi-levels populate object.

Can't get it to work with Express

I'm using express, as shown in your example in the README.

For this use case /users?age>18 for example, express fails to parse the query correctly, and req.query is an empty object. The same problem occurs whenever the operator is not =.

This problem is not directly related to this package, but I don't see the point of including as an example in the README if it does not work for many operators. And if it doesn't work with Express, the use cases are very limited.

As an alternative, I tried grabbing req.originalUrl, but the age<18 part is already filtered out.

And I know that I can use the filter key directly, but it is less convenient.

Is there something I'm missing?

aqp attempts to autoconvert SOME uuids to dates

Can be tested with the following Jest test

const aqp = require('api-query-params')
describe('AQP test', function () {
  it('should test our aqp guid parsed as date error', done => {
    const notWorking = 'uuid=c0974f20-1071-11e7-81c3-a593aec7037f'
    const working = 'uuid=1e78b83e-d1d7-4b62-9472-d0ebf8531091'

    let aqpResult = aqp(working)
    expect(aqpResult.filter.uuid).toBe('1e78b83e-d1d7-4b62-9472-d0ebf8531091')

    aqpResult = aqp(notWorking)
    expect(aqpResult.filter.uuid).toBe('c0974f20-1071-11e7-81c3-a593aec7037f')
    done()
  })
})

RESULT:


Expected: "c0974f20-1071-11e7-81c3-a593aec7037f"
    Received: Date { NaN }

    Difference:

      Comparing two different types of values. Expected string but received date.

Can I use my query parameters together with it ?

If I do this will it work

 const desiredBook= await Book
    .find(filter)
    .skip(skip)
    .limit(recordsPerPage)
    .sort(sort)
    .select(projection)
    .populate(population)
      .or(["audiobook.book.bookname":searchQuery,
      "pdf.author.authorname":searchQuery,
      "book.booktitle.bookTitleName":searchQuery ,        
     "location.location.location":searchQuery 
      ])
    .sort(sort);

or is there a way I could do this

const { filter, skip, limit, sort, projection, population } =
    aqp(`filter={ "$or": [
      {"audiobook.book.bookname": "${searchQuery}" },
      {  "pdf.author.authorname": "${searchQuery}" },
      { "book.booktitle.bookTitleName": "${searchQuery}" },
      { "location.location.location": "${searchQuery}" },
 ]}`
 , { skip: ((req.params.page * recordsPerPage) - recordsPerPage), ...req.query });

[request] Option to disable automatic casters

Hi, I don't like the unpredictability that automatic casting adds to the application. I may have dozens of string fields and in some of them the user might decide to use only numbers. I don't want to have to declare all my string fields explicitly (castParams)

Might you include an option to disable automatic casters?

Typo in the example

I believe the following sort and limit variables are undefined and it should be read from the query.

app.get('/users', (req, res, next) => {
  const query = aqp(req.query);
  User
    .find(query.filter)
    .skip(skip)
    .limit(limit)
    .sort(sort)
    .exec((err, users) => {
      if (err) {
        return next(err);
      }

      res.send(users);
    });
});```

aqp is not a function afterr upgrading from 4.5.1 to 4.8.0

const aqp = require('api-query-params')

Just calling aqp from any method I got this error:

TypeError: apq is not a function

Tested on node 8.9.4 and 10.10.0. And know the I don't give any details, but I don't get more information on this.

Querying by string with / incorrectly parsed

I am trying to filter by a string that contains one of more / characters.

Query: {key: 'This key/has special chars!'}

Where the console.logs are:

console.log(ctx.query)
const { filter, skip, limit, sort, projection } = aqp(ctx.query)
console.log(filter)

What that console.log looks like:

{ key: 'This key/has special chars!' }
{ key: { '$in': [ 'This key', 'has special chars!' ] } }

Expected result: the string for key: is preserved as a single string for the mongoose query.

Error with casters and )

Hi and sorry for my poor english.
I've a problem with ')' char when i try to force cast with string() or use casters with a custom function.
To reproduce the issue:

  const val = aqp("key1=string(abc))");
  console.log(val);

  const val2 = aqp("key1=stringPers(abc))", {
    casters: {
      stringPers: (val: string) => val,
    },
  });
  console.log(val2);

with '(' char works very well.

A workaround seems to use castParams that works with ')' but fails with ',' (the key is converted in array with $in operator)

To reproduce:

const val3 = aqp("key1=a,b,c,)&key2=abc)", {
    castParams: {
      key1: "string",
      key2: "string",
    },
  });
  console.log(val3);

So i haven't still find a way to cast a key with ')' and ',' to simple string

Thanks for help and compliments for your package, is very useful.

Filippo

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.