Coder Social home page Coder Social logo

hapi-swagger's Introduction

hapi-swagger

This is a OpenAPI (aka Swagger) plug-in for Hapi When installed it will self document the API interface in a project.

Maintainers Wanted GitHub Workflow Status npm downloads MIT license

Compatibility

Version Hapi Joi Node Release Notes
17.x >=20.0.0 @hapi/hapi >=17.0.0 joi >=16 Release
16.x >=20.0.0 @hapi/hapi >=17.0.0 joi >=14 #795
15.x >=20.0.0 @hapi/hapi >=17.0.0 joi >=14 #782
14.x >=19.0.0 @hapi/hapi >=17.0.0 joi >=12 #680
13.x >=19.0.0 @hapi/hapi >=17.0.0 @hapi/joi >=12 #660
12.x >=19.0.0 @hapi/hapi >=17.0.0 @hapi/joi >=12 #644
11.x >=18.4.0 @hapi/hapi >=16.0.0 @hapi/joi >=8 #631
10.x >=18.3.1 @hapi/hapi >=14.0.0 @hapi/joi >=8 #587
9.x >=17 hapi <14.0.0 >=8 #487
7.x <17 hapi ??? ??? #325

Installation

You can add the module to your Hapi using npm:

> npm install hapi-swagger --save

hapi-swagger no longer bundles joi to fix #648. Install hapi-swagger with peer dependencies using:

npx install-peerdeps hapi-swagger

If you want to view the documentation from your API you will also need to install the inert and vision plugs-ins which support templates and static content serving.

> npm install @hapi/inert --save
> npm install @hapi/vision --save

Documentation

Quick start

In your Hapi apps please check the main JavaScript file and add the following code to already created a Hapi server object. You will also add the routes for you API as describe on hapi website.

const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const Vision = require('@hapi/vision');
const HapiSwagger = require('hapi-swagger');
const Pack = require('./package');

(async () => {
    const server = Hapi.server({
        port: 3000,
        host: 'localhost'
    });

    const swaggerOptions = {
        info: {
                title: 'Test API Documentation',
                version: Pack.version,
            },
        };

    await server.register([
        Inert,
        Vision,
        {
            plugin: HapiSwagger,
            options: swaggerOptions
        }
    ]);

    try {
        await server.start();
        console.log('Server running at:', server.info.uri);
    } catch(err) {
        console.log(err);
    }

    server.route(Routes);
})();

Tagging your API routes

As a project may be a mixture of web pages and API endpoints you need to tag the routes you wish Swagger to document. Simply add the tags: ['api'] property to the route object for any endpoint you want documenting.

You can even specify more tags and then later generate tag-specific documentation. If you specify tags: ['api', 'foo'], you can later use /documentation?tags=foo to load the documentation on the HTML page (see next section).

{
    method: 'GET',
    path: '/todo/{id}/',
    options: {
        handler: handlers.getToDo,
        description: 'Get todo',
        notes: 'Returns a todo item by the id passed in the path',
        tags: ['api'], // ADD THIS TAG
        validate: {
            params: Joi.object({
                id : Joi.number()
                        .required()
                        .description('the id for the todo item'),
            })
        }
    },
}

Once you have tagged your routes start the application. The plugin adds a page into your site with the route /documentation, so the the full URL for the above options would be http://localhost:3000/documentation.

Typescript

hapi-swagger exports its own typescript definition file that can be used when registering the plugin with Hapi. See example below:

Install Typescript Definition Files

npm i @types/hapi__hapi @types/hapi__inert @types/hapi__joi @types/hapi__vision @types/node hapi-swagger --save-dev

Register Plugin with Typescript

import * as Hapi from '@hapi/hapi';
import * as HapiSwagger from 'hapi-swagger';

// code omitted for brevity

const swaggerOptions: HapiSwagger.RegisterOptions = {
    info: {
        title: 'Test API Documentation'
    }
};

const plugins: Array<Hapi.ServerRegisterPluginObject<any>> = [
    {
        plugin: Inert
    },
    {
        plugin: Vision
    },
    {
        plugin: HapiSwagger,
        options: swaggerOptions
    }
];

await server.register(plugins);

Contributing

Read the contributing guidelines for details.

Thanks

I would like to thank all that have contributed to the project over the last couple of years. This is a hard project to maintain, getting Hapi to work with Swagger is like putting a round plug in a square hole. Without the help of others it would not be possible.

hapi-swagger's People

Contributors

amitnovick avatar andriinyzhnyk avatar appeninterencebarbe avatar bohdan-kalynovskyi avatar caligone avatar codingstill avatar darinc avatar dependabot[bot] avatar edimoldovan avatar felipeleusin avatar fhemberger avatar glennjones avatar jisack avatar johnbrett avatar larromba avatar lewisdaly avatar marsup avatar mattboutet avatar matthieusieben avatar michaelkuk avatar petermanser avatar rluba avatar robmcguinness avatar rokoroku avatar ruionwriting avatar sk-fcomputer avatar smgn avatar tornquist avatar xsalazar avatar zachhaber 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hapi-swagger's Issues

Joi 2.0

If this is still being maintained, you might want to pay attention to the Joi 2.0 changes coming in hapi 1.16. The internals are all different for validation rules.

UI doesn't invoke URIs with optional path parameters

It looks like the Swagger UI has a problem with hapi-style optional path parameters. With a route defined as:

      /books/{id}/cover

Swagger UI has no trouble at all. It documents the route, and enables me to invoke it from the UI. Great!

But when a parameterized path contains an optional parameter at the end, it has a problem:

    /library/shelves/{shelfId}/book/{id?}

Here it documents the route, including the optional parameter segment. But when I try to execute the from the UI, leaving the optional parameter blank, the UI never invokes the API.

Did I miss something in the documentation? Or is this an issue?

invalid default value when for an array

Hi

I have a route with query validation:

validate: {
  query: {
    field: joi.array().default([ 'field1', 'field2', 'field3' ])
  }
}

The default value in the "try it out" section is field1, field2, field3
image

This is the wrong way to write an in a query parameter and it leads to bad request error.

It should have been ["field1", "field2", "field3"]

Consumes is not configurable

It is hard coded to application/json. The UI should be able to send any text based format as long and the API can deal with it. Hapi will allow any text/* type through untouched for a plug to deal with it.

Joi Schema cached?

I apologize in advance for the long winded explanation. ๐Ÿ˜€

I have a ApiResponse schema that is the base schema for all of my api responses.
Seeing as the property result can contain many different schemas, I wrote a "generator". (See below)

The Problem I ran into is that since this schema always has the same name, any route with a different result schema, would just repeat the definition of the first.

For example, with following routes result will both have the User.schema in swagger-ui.

  • route 1: response.schema: ApiResponse.schema User.schema
  • route 2: response.schema: ApiResponse.schema Authorization.schema

However If i swap the order that they are loaded, result (of both routes) will now contain Authorization.schema

I further proved this by dynamically generating the class name of ApiResponse.schema (commented out below) and the problem vanished.

However, I wish for the ApiResponse class name to remain the same for all routes, despite the schema of result being different. Is this possible?

class ApiResponse
  (@status-code, { @error, @message, @result }:details) ->


  @schema = (result-schema) ->
    Joi.object {

      status-code: Joi.number!
        .integer!
        .required!

      error: Joi.string!

      message: Joi.string!

      result: result-schema

    } .options {
      class-name: \ApiResponse #+ result-schema._settings.class-name
    }

Refactor - increase test coverage

The plug-in needs to be refactored and based more heavily on good test coverage. The constant changes to HAPI have brought the project to a point it needs to be recoded. I going to open up a dev branch and start to work soon.

I will try and keep up with fixes and any more breaking changes from HAPI in the meantime.

Generate docs for multiple APIs

For my use case, I generate for several clients an API, they all have their seperate path, i.e. /client-a or /client-b.

What I'd like to do is generate API docs for each client seperately. So that /client-a/docs shows all available routes for Client A und /client-b/docs everything client B need to know.

If I understand the current implementation correctly, this is not yet possible. Is that correct? My suggestion would be to add another tag to all routes that belong to one client and then be able to generate docs for all routes that e.g. have the tags api and client-a.

I'd be happy to dig into that and contribute this functionality. I just wanted to make sure, that this is not yet possible, before I start to implement.

'Try it out!' not sending payload

I have a POST method with a required fields and an optional field. I input text into the required field and hit the 'Try it out' button but it is failing validation stating that the required field wasn't specified.

Sticky Payload and Response Schemas

The first time I set a payload or response schema in an array of routes it is stuck and even if I define another schema in a payload or response for the same route or one with a different method.

I was trying to have a POST body with attributes and return a response object which was the same and also included an 'id' attribute that wasn't present in the payload.

All payloads are validated properly. They are just displayed in the UI improperly.

Keep up the great work!

Payload definition title

Not urgent - but I noticed the payload definition model acts like in the image attached, where the title is model_ e.g. model_0_1_2 and so on:

image

Where as the swagger project lists them like:

image

Is this a small bug or is it likely I've something configured incorrectly? Will look into the code tomorrow, just wanted to point it out first in case it was just my config or something like that.

Non-JSON payloads

I see form-url-encoded payload support was removed. Shouldn't the payload type be a setting?

Multiple Joi.object() with className break docs

If I generate schema:

var schema = Joi.object({
   _id: Joi.any().required(),
   name: Joi.string().required()
}).options({
   className: 'User'
});

that I include it let's say in response schema and then - I modify it like below:

var schema2 = schema.keys({
    name: Joi.string().optional()
});

or

var schema2 = schema.optionalKeys('name')

Swagger UI shows first schema (schema1) everywhere instead of displaying schema2 in request parameters.

After removing className, everything works as expected. This might be a bug due to className resolving.

'resourceName' is not defined

Hi Glenn

In the latest release (0.0.4) I'm getting an error thrown on line 55 of hapi-swagger/lib/index.js:

'ReferenceError: resourceName is not defined'

This is the code that throws the error:

if(resourceName){

but I don't see resourceName used or defined anywhere else other than as an input param to the new function 'isResourceRoute'. Where is that value meant to come from?

David

Forms sending undefined args in swagger.js

There is still an issue with form arguments where swagger.js will send undefined for any fields that are empty (ie. so you'll receive something like foo=something&bar=undefined )
I posted about this at the swagger project and I think they fixed it in a recent version (they fixed the same bug for query args, although not perfectly.) For now I've fixed my local copy in hapi-swagger. It's only a few lines of code.

swagger.js line 967:

    for(var i = 0; i < formParams.length; i++){
      var param = formParams[i];
      var value = this.params[param.name];
      if (value !== undefined) {
        values[param.name] = value;
      }
    }

    var encoded = "";
    for(key in values) {
      value = values[key];
      if(encoded !== "")
        encoded += "&";
      encoded += encodeURIComponent(key) + '=' + encodeURIComponent(value);
    }

joi allow produces select ignoring other cases

validating route path param with Joi.string().regex(/^[0-9a-fA-F]{24}$/).allow('me') - produces select with option me, so we cant test any other valid path params.

maybe use Joi Any.example method in this case to populate options or just leave test input and add extra info about allowed values?

Not install with hapi from npm

hapi on npm is version 2.2.0

npm ERR! peerinvalid The package hapi does not satisfy its siblings' peerDependencies requirements!
npm ERR! peerinvalid Peer [email protected] wants hapi@~2.1.1

npm ERR! System Linux 3.11.0-15-generic
npm ERR! command "/usr/bin/node" "/usr/bin/npm" "install" "hapi-swagger" "--save"
npm ERR! cwd /home/deskx/Aplicativos/node.ssh/ssh.api
npm ERR! node -v v0.10.25
npm ERR! npm -v 1.3.24
npm ERR! code EPEERINVALID
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR! /home/deskx/Aplicativos/node.ssh/ssh.api/npm-debug.log
npm ERR! not ok code 0

Make compatable with Hapi 5.x.

@glennjones I'm wondering if you anticipate updating hapi-swagger so support the latest version of hapi?

There are some breaking changes, mostly around joi. I might be able to submit a PR with the changes if you don't see any breaking changes that will be big blockers.

Thanks

Supporting enums

The swagger spec supports enums to specify pre-defined options:

"status": {
          "type": "string",
          "description": "Order Status",
          "enum": [
            "placed",
            " approved",
            " delivered"
          ]
        },

I know that joi doesn't support the enum() function. Any idea how we could add this here?

Routes w/o trailing slash are ommitted from doc

Hi,

Thanks for building a really cool self-documentation tool. I have a quick question regarding a missing endpoint from the doc.

It seems like that the plugin omits the main GET for a resource. For example, say I have these routes (all which are tagged: ['api']):

var routes = [
  { path: '/movies',        method: 'GET',    config: handlers.movies.list      },
  { path: '/movies/{id}',   method: 'GET',    config: handlers.movies.get       },
  { path: '/movies/{id}',   method: 'DELETE', config: handlers.movies.delete    },
  { path: '/movies/{id}',   method: 'PUT',    config: handlers.movies.update    },
  { path: '/movies',       method: 'POST',   config: handlers.movies.add       }
];

The doc omits GET /movies, but it does show GET /movies/:id and all other routes with a trailing slash per the library:

//lib/index.js
routes = routes.filter(function (item) {
    if (request.query.path &amp;&amp;
        item.path.indexOf('/' + request.query.path + '/') !== 0 ) {
        return false;
    }
    return item.settings.plugins['hapi-swagger'] !== false &amp;&amp; item.method !== 'options';
});

Was there a particular train of thought behind this? Simply removing the + '/' from the filter seems to fix the issue. I'd be happy to submit a pull request for this.

incompat with hapi 2.0

Swagger is using server.routingTable(). Should be using server.table() by the time 2.0 is official.

Crypto defined globally

I noticed that there is small issue with declaration of Crypto module. It is defined like this:


var Hoek = require('hoek'),
    Boom = require('boom'),
    Joi = require('joi'),
    Path = require('path');
    Crypto = require('crypto');

Instead of ; there should be , after Path declaration.

6.x error when payload attached

Hi Glenn,

Can't thank you enough for 6.x support, was in the middle of making a PR when I saw the commit. It all works nearly perfect, but I've found one issue though, if you have a route with payload in the validation object it prevents the swagger-ui from loading with the error:

[ 'start' ] 'be-more-hapi - web interface: http://localhost:3000'
Debug: hapi, internal, implementation, error 
    TypeError: Uncaught error: Object.keys called on non-object

It works fine for validating params and query, just a problem with payload. Will try to debug and make a PR, but I'm sure you've a better idea of what's happening.

Thanks again for the quick response in providing 6.x support.

Do you know how to work with optional parameters

I have a route like
/{version}/services/{cc_}
{cc_} is an optional parameter in Hapi, but when I leave the field parameter empty, the rest call is not executed, do you know how to make this work

thanks

Responses classes

Is anyone working on support for responses and response class definitions etc.? If not, we'll do a fork to add that.

model declaration overwritten when having more than one method with same payload structure

so let's say, one method has:

    path   : '/one_method'
    config :
        validate:
            payload:
                data: t.object().keys
                    something : t.string().required()
                    anything   : t.string().default( "ios" )

and the oner one:

    path   : '/another_method'
    config :
        validate:
            payload:
                data: t.object().keys
                    name : t.string().required()
                    age   : t.string().required()

Both will result with duplicated documentation for "payload.data".

Basically then any method which contains "data" as "child" of "payload" will result with identical documentation @ swagger UI.

When checking the json generated by "/docs", i could spot that it exports 1 "model" per "object" inside of the payload, so i believe the /docs json structure will have to change in order to honour the data.

Displaying a notes array as Implementation Notes

If route notes is an array of strings (as Hapi allows), it gets displayed by swagger-ui as a single string with each note separated by a comma. Not sure how other people are using notes but we are using Hapi notes as an array with each string being a paragraph of text.

After hapi-swagger pulls out the "special notes" (response codes, response class), I would like to see it build all the remaining notes strings into a single string separated by

markers. Then swagger-ui will render it nicely as multiple paragraphs. Any thoughts?

"path" and "config.validate.payload.<arrayName>" issue

Hello guys,

I think i found an issue in the case where the route path variable name matches its payload name. When they are the same strings, the model schema will not show the right detail.

Here is simplified example of what i mean.

exports.routes = [
        {
        path: '/students',
        config: {
            //some more code here
            tags: ['my_tag'],
            validate: {
                payload: {
                    students: Joi.array().required().min(1).includes(validator.schema)
                },
           }
        }
]

The validator.schema contains several fields required for each 'students' element in the array,
none of these fields would show up under the model schema of the hapi-swagger page when the array name (students) matches the path name (students)

Throbber not found when using prefixed endpoints

I usually run into this issue every update, so thought I'd mention it in case there may be an easy work around.

I version all my endpoints e.g. /v2/item and so on, so every swagger resource is loaded under the prefix /v2, which works fine, as my page that loads swagger I can define the resource locations as being under /v2, e.g.

    <script src='/v2/docs/swaggerui/lib/shred.bundle.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/jquery.slideto.min.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/jquery.wiggle.min.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/handlebars-1.0.0.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/underscore-min.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/backbone-min.js' type='text/javascript'></script>
    <script src='/v2/docs/swaggerui/lib/swagger.js' type='text/javascript'></script>

However, I can't specify the throbber.gif url as it's hardcoded in the swagger-ui.js file here: https://github.com/glennjones/hapi-swagger/blob/master/public/swaggerui/swagger-ui.js#L355

I can easily go in and edit this (I know that's bad practice, but I don't know of an alternative!) to be
<img alt='Throbber' class='response_throbber' src='/v2/docs/swaggerui/images/throbber.gif' style='display:none' /> which works fine, It just mean this has be done every time I update hapi-swagger or the throbber gif doesn't load. Do you know of any work around for this or fix I could provide? I imagine it's likely an edge case so I'll accept a 'wont fix' :)

Duplicated payload keys overwrites extended schemas on docs

I'm having this issue when using same key on different configurations as detailed below:

var Person = Joi.object({
  ssn: Joi.string().required()
});

var CreatePerson = Person.keys({
  password: Joi.string().required()
});

var UpdatePerson = Person.keys({
  password: Joi.string()
});

...

server.route([
  {
    method: 'POST',
    path: '/person',
    config: {
      handler: handler,
      tags: ['api'],
      validate: {
        payload: {
          person: CreatePerson.required()
        }
      }
    }
  },
  {
    method: 'PUT',
    path: '/person',
    config: {
      handler: handler,
      tags: ['api'],
      validate: {
        payload: {
          person: UpdatePerson.required()
        }
      }
    }
  }
]);

In both endpoints, person.password is shown as required. Here's a Gist with a functional example:

https://gist.github.com/dmacosta/f7489aad15ce85798b86

Notice replacing both person keys into something different like a and b shows correct schemas.

recursively adding fields for object properties ?

Hello Guys,

First of all, absolutely great work! The plugin is amazing!

I'm wondering if there is a way of "recursively" add the properties for the "payload" ?

Getting this method as an example:

screenshot 2014-11-10 00 13 29

Would is be possible to generate one field for each property of "data" and "session", instead of expecting one json string representation of the object?

If not, could you please consider this as a feature request?

Unfortunately i have to keep backwards compatibility in this implementation so i can't "promote" the properties to be one level up : (

Anyway i think ( and hope ) this feature would be very useful for many users.

Thank you very much

Forbidden keys are still present in generated API

As above, if presence set to forbidden, attribute shows as either optional or required (for example, if basic schema defines all the attributes as required ones and the custom schema that produces that errors defines that one attribute is forbidden - let's call it _id).

if (param._flags && param._flags.presence) {
   property.required = (param._flags.presence === 'required') ? true : false;
}

Something like this if (param._flags.presence === 'forbidden') return; at some point in the code would do the trick (as value is forbidden - we don't want our users to see it in the schema)

arrays and objects param models

Is there anything in the pipeline to extract model schemas from hapi validation for array and object parameters?

I'd be interested in looking into this.

headers are not supported parameters

headers are not supported parameters...

E.g.


        validate: { 
            headers: {
                authorization: joi.string()
                    .required()
                    .description('authorization')
            },
            payload: models.member.schema
        }

Support for Hapi 8

Hi Glenn,

Have you plans to support the latest Hapi version? Anything I can do to help? I don't believe there were much changes to the plugin API, just the addition of reply.continue() was the only required update to some of my plugins.

Thanks again for providing this plugin!

arrayProperty['enum'] code logic switched?

I was running into a bug related to an array as the first responseObject and I'm working on a fix for it. I can't tell for sure, but it looks like the logic in this block of code may be reversed. Can you take a look at it and clarify?

            if (arrayProperty['enum']) {
                property.items = {
                    '$ref': arrayProperty.type
                };
            } else {
                property.items = {
                    'type': arrayProperty.type,
                    'enum': arrayProperty['enum']
                };
            }

shouldn't it be:

            if (arrayProperty['enum']) {
                property.items = {
                    'type': arrayProperty.type,
                    'enum': arrayProperty['enum']
                };
            } else {
                property.items = {
                    '$ref': arrayProperty.type
                };
            }

If so, I will include that change in a pull request I'm getting ready to submit.

Please update Hapi compatibility

I love what this plugin can do, but beyond Hapi v4, it is complaining due to what appears to be some minor schema changes in the validation definitions from Hapi. In addition to that Eran Hammer announced breaking changes coming with version 6 of Hapi that directly impacts how server plugins are registered. He has posted details on how to migrate existing plugins to the upcoming 6.0 release and maintain backwards compatibility. ( hapijs/hapi#1664 ) This should probably be listed as 2 separate issues, but they are both related with maintain compatibility with newer versions of Hapi.

Missing alternatives support

Joi has possibility to define alternatives. Hapi-swagger will just make following markup for that:

hello {
files (alternatives)
}

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.