TypeScript icon, indicating that this package has built-in type declarations

    1.1.3 • Public • Published

    jinqu-odata - Javascript-Linq to Odata

    Build Status Coverage Status npm version Known Vulnerabilities GitHub issues GitHub license

    GitHub stars GitHub forks

    Jinqu-odata lets you write LINQ queries against an odata source. For those who don't know LINQ, the benefits are:

    • A unified query language, whether querying local arrays, odata sources, or any other remote data source
    • Static typing where Typescript can verify your query is sound

    jinqu-odata is dependent on the jinqu package.


    npm install jinqu-odata


    First, we need classes that map to our odata resources. For example:

    export class Book {
        Id: number
        Title: string

    We can now query filtered books as follows:

    const service = new ODataService ("https://www.solenya.org/odata")
    const books = await service
        .where(b => b.Price > 60)
    for (var b of books)
        console.log (b)

    You can play with the live sample here

    The query is translated to the following odata url:

    https://www.solenya.org/odata/Books?$filter=Price gt 60

    Inheriting from ODataService

    A common pattern is to inherit from ODataService to provide stubs for your odata resources as follows:

    export class CompanyService extends ODataService {
        constructor (provider?: IAjaxProvider) {
        companies() {
            return this.createQuery(Company)

    Code Generation

    Currently we don't have code generators for jinqu-odata. However, we're actively considering this feature and it's tracked by this github issue:


    LINQ to OData Translation

    jinqu-odata translates LINQ queries to OData Version 4 query strings. In the quries that follow, translations are shown as comments. You can check the unit tests for more thorough coverage of the translations.


    To filter results we use the where operator:

    const result = await query
        .where(c => c.name.startsWith('Net'))
    // odata/Companies?$filter=startsWith(name, "Net")

    Supported Operators

    Name TypeScript/JavaScript OData
    Equals ==, === eq
    Not Equals !=, !== ne
    Greater Than > gt
    Greater Than or Equal >= ge
    Less Than < lt
    Less Than or Equal <= le
    Logical And && and
     Logical Or ||  or 
    Logical Not  !  not
     Arithmetic Add + add
     Arithmetic Subtraction - sub
     Arithmetic Multiplication * mul
     Arithmetic Division / div
     Arithmetic Modulo % mod
     Arithmetic Negation - -

    Supported Inline Functions

    TypeScript/JavaScript OData
    includes substringof
    endsWith endswith
    startsWith startswith
    length length
    indexOf indexof
    replace replace
    substring substring
    toLowerCase tolower
    toUpperCase toupper
    trim trim
    concat concat
    getMonth month
    getDate day
    getHours hour
    getMinutes minute
    getSeconds second
    Math.round round
    Math.floor floor
    Math.ceiling ceiling


    The select operator lets us select only a subset of the fields of a type. It can only occur as the last operator in a query, so must be awaited:

    const result = await query.select("name")
    // $select=name


    The orderBy operator, optionally followed by some thenBy operators, specifies result order:

    const result = await query
        .orderBy(c => c.category)
        .thenByDescending(c => c.created).toArrayAsync()
    // $orderby=category,created desc


    To get the count of a resource:

    const count = await query.count()
    // Companies/$count will be executed

    Skip and Take

    We can skip a number of items, or limit the number of items, by calling skip and take. Here we query for the 3rd page in a result, by skipping the first 20 results, and then returning the top 10 of the remaining results:

    const result = await query.skip(20).take(10).toArrayAsync()
    // $skip=20&$top=10


    We can use the inlineCount operator to include the inlineCount property on the results. This will cause query to wrap result.

    const result = await query.inlineCount().toArrayAsync()
    const value = result.value
    const inlineCount = result.inlineCount // only populated if inlineCount operator was called

    This is useful in the preceding skip/take scenario, where to implement paging, we'd like the result to include a total non-paged count, without having to write a separate query. Just add the inlineCount operator before calling skip/take.


    jinqu-odata supports expand, which enables you to pull in related entities. In this example, we don't merely want to return books; we also want to return the press associated with each book. We can do this as follows:

    const companies = await service
    // books$expand=Press

    Nested Expand

    Sometimes we want to drill down more than one level. In this example, our odata source has books, where we want to return all the authors for some books. However, since books can have multiple authors, there's a join table between Authors and Books. Our model will mirror the odata metadata as follows:

    export class Book {
        Title: string
        @Type(() => AuthorBook) AuthorBooks: AuthorBook[]
    export class AuthorBook {
        @Type(() => Author) Author: Author
    export class Author {
        Name: string

    To query, we first expand the AuthorBooks property, and thenExpand the Book property, as follows:

    const books = await service
    // books?$expand=AuthorBooks($expand=Author)

    Filtering Expand by Rows and Columns

    For efficiency, we can filter by rows an expand/thenExpand query by providing a predicate:

    .thenExpand("Author") // no filter
    .thenExpand("Author", a => a.endsWith ("Albahari")) // filtered
    // books?$expand=AuthorBooks($expand=Author($filter=endswith(Name,'Albahari')))

    Similarly, for efficiency, we can filter by columns an expand/thenExpand query by providing an array of column names:

    .thenExpand("Author") // no filter
    .thenExpand("Author", ["Name"]) // filtered columns
    // books?$expand=AuthorBooks($expand=Author($select=Name))


    The @Type decorators belong to the class-transformer library that handles deserialization. We need those annotations since the typescript types aren't actually available at runtime. The class-transformer library imposes the small design restriction on us that any constructor arguments to our classes are optional.


    groupBy lets you group results by a particular property. Like select, it can only be used as the last operator in a query, and must therefore be awaited:

    // we group resources by "deleted" field
    // and select the count of each group with "deleted" field
    const promise = await query.groupBy(
        c => ({ deleted: c.deleted }),
        g => ({ deleted: g.deleted, count: g.count() })
    // $apply=groupby((deleted),aggregate(deleted,$count as count))

    As you can see in the translation, jinqu-odata supports groupBy with the $apply convention.

    Old Browsers

    jinqu-odata uses jinqu as a querying platform, if you want to use jinqu features with old browsers, please refer to jinqu documentation.


    jinqu-odata is licensed under the MIT License.


    npm i jinqu-odata

    DownloadsWeekly Downloads






    Unpacked Size

    155 kB

    Total Files


    Last publish


    • umutozel