mpashkovskiy / express-oas-generator Goto Github PK
View Code? Open in Web Editor NEWOpenAPI (Swagger) specification generator for ExpressJS applications
License: Apache License 2.0
OpenAPI (Swagger) specification generator for ExpressJS applications
License: Apache License 2.0
How to add detail of documentation (description, defenition, etc) for each endpoint ?
And how to implement "Try out" functionality of each endpoint?
This is so good! but i still have problem to complete the interactive api-docs.
Thanks
considering this example (taken from express site):
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
the generator will parse the uri but not the parameters and body unless we explicitly call to next:
const express = require('express')
const app = express()
app.get('/', (req, res, next) => {res.send('Hello World!')); next();}
app.listen(3000, () => console.log('Example app listening on port 3000!'))
I know that this route not have any use of params and body, this is only for the example 😄
Creating this issue as a roadmap (sorted by priority):
Still in 1.X
:
production
env #64: PR #69README.md
, focused on "advanced
" (unique) usage.2.X
major version:
What do you think @kiprasmel @mpashkovskiy ?
express-oas-generator is the salvation of my situation right now. Thank you
I have a question.
Is there a way to request a definition for the request body? or
Is there a way to predefine the response?
Or is there any possibility to add to that function?
Thank you again
prices.js
const express = require("express");
const router = express.Router();
const { soap } = require("strong-soap");
const urlv6 = "./wsdl/series_data_v6.WSDL";
const clientPromise = new Promise((resolve, reject) =>
soap.createClient(urlv6, {}, (err, client) =>
err ? reject(err) : resolve(client)
)
);
router.post("/", function(req, res, next) {
clientPromise
.then(client => ({ client, request: req.body }))
.then(invokeOperations)
.then(results =>{
res.json(results);
next();
})
.catch(({ message: error }) => {
res.json(error);
next(error);
});
});
const invokeOperations = ({ client, request }) =>
new Promise((resolve, reject) => {
client.setSecurity(
new soap.BasicAuthSecurity(process.env.SEB_USER, process.env.SEB_PASSWORD)
);
client.RequestSeriesData(request, (err, result) => {
err ? reject(err) : resolve(result);
});
});
module.exports = router;
I get the following error Error: Can't set headers after they are sent.
I read the other issues and i noticed if I don´t call next()
then no parameters will be picked up but when i use next()
it complains about my header being set.
Current init function header:
express-oas-generator/index.js
Line 145 in c6c6ec8
I think that it would make the call of the init
function easier when the optional arguments are passed as an object with default params like:
module.exports.init = (aApp, aPredefinedSpec, { aPath, aWriteInterval, aApiDocsPath } = { aPath: '', aWriteInterval: 10 * 1000, aApiDocsPath: 'api-docs'}) => {...}
First of all, thanks for this GREAT tool! I've alrady given you a star and promoted the tool on stackoverflow. (This was not related to the feature request)
I appreciate the possibility to specify a baseUrlPath
in the package.json
file.
It would be useful, however, if I could drive the base URL using an environment variable during Docker deployments.
My suggestion is something similar as:
let app = express();
expressOasGenerator.init(app, {
baseUrlPath: process.env.BASE_URL
});
Any suggestions / objections? Thanks!
It needs a recursive code here
EDIT: made it work in a fork here, I will not make a PR cause I copied some code from express-list-endpoints that I dont even know what it does :P
Hi guys! I've got a problem: I can't reach localhost:3000/api-docs: it gives me "ERR_TOO_MANY_REDIRECTS".
This is how I init module from "ExpressServer.js", the file that starts my express server (app) and swaggerDocument is json file that wraps all specs.
expressOasGenerator.init(app, swaggerDocument);
Any hint of why this is happening?
Thank in advance.
Sometimes it happens that certain tags don't match any route. Remove them from docs.
Plus: Pluralize mongoose model tags to check matches.
Hi when I try to use your generator I get : predefinedSpec is not a function.
I"m not doing anything fancy... just the plain example :
expressOasGenerator.init(this.app);
I'm using "this" because my code is in typescript.
any idea ?
Originally from #49 (comment)
We'll need something like
npm i mkdirp
const mkdirp = require('mkdirp');
const path = require('path');
const dir = path.parse(specOutputPath).dir
mkdirp.sync(dir);
It would be awesome to group operations by tags:
https://swagger.io/docs/specification/grouping-operations-with-tags/
By setting a flag:
expressOasGenerator.handleResponses(server, {
specGroupByTag: true
});
You could group by the first route path after base url (aka controller name). REST example:
users
users
/:idusers
All those endpoints could be tagged as users.
Thanks in advance!
TypeScript code base:
I have routes as follows
this.router.get('/list', this.userCtrl.fetchAllUsers);
this.router.get('/:id', this.userCtrl.fetchById);
this.router.post('/create', this.userCtrl.createUser);
this.router.put('/:id', this.userCtrl.updateById);
this.router.delete('/:id', this.userCtrl.deleteById);
this.router.patch('/:id', this.userCtrl.fetchById);
parent route is defined as follows
this.app.use('/api/user', new UserRoute().getUserRoutes());
When i open the http://localhost:3000/api-docs
Hello i'm trying out your package its great but i don't find how to put jwt token into swagger or attach to request
I was finding that some of my response schemas looked corrupt. Upon deeper inspection, the buffer assembled from the chunks in updateResponses was compressed (the content-encoding header was gzip). I disabled compression in my dev environment as a workaround.
I'm not sure how to create the example calls for my API.
While doing it manually with postman worked and it added the examples, I couldn't manage to make it work with a jest test suite using supertest.
Is there a way to do with a test suite instead of having to make the request manually?
If so, can you provide an example?
Hello!
First I'd like to congratulate you for your good work with this project!
And here we go: I actually can't manage to show in the Swagger's UI the description of each parameter... Is there specific way of do it?
Thank you in advance!
Is there any way to get the body parameters to get into the api doc automatically?
In order to have mongoose model definitions, it could be pretty useful to integrate a library such as:
Matching route requests/responses with model definitions must be hard to implement.
Just a read-only implementation would be great!
An interface like this could work:
expressOasGenerator.handleResponses(server, {
specMongooseModelsPath: './models',
specMongooseIncludeModels: ['User', 'Blog', ...],
});
Thanks in advance!
I love the option for outputting specification to a .json
file. However the problem I face is, the file grows so large. Based on my usage pattern, some of the routes get tons of examples added. And some others don't have any yet.
It will be great if we can tightly control what goes into the specification json. Things like, store only 1-2 examples per api.
I believe the second argument to init()
is for this purpose. But I am not sure how exactly to tweak it for the above need.
I will be happy to create a documentation PR if anybody can shed some light here.
The first request in my projects only creates documentation for the routes, not for the responses. When running the same request more than once, the response is added to the documentation.
(Tried experimenting with writeIntervalMs and other settings, but can't get it to work.)
I've created a test repo demonstrating the issue. This is extra bad since the best use case for express-oas-generator is to use it with unit tests, those promoting both TDD and swagger doc without having to do it manually with annotations etc.
Example repo:
https://github.com/jens-peterolsson/test-express-oas-generator
npm start
then http://localhost:5000/data
or just
npm test
Spec is merged with swagger.json.
Init
won't be used anymore, need to ship a major version.
Only advanced
usage (from now) will be valid: handleResponses
and handleRequests
middlewares.
Adjusting README.md
+ examples accordingly.
Thanks for the library. I am using version 1.0.7
Below is my app.js code here i am using express-oas-generator
var express = require('express');
var app = express();
const expressOasGenerator = require('express-oas-generator');
var bodyParser = require('body-parser');
var port = process.env.PORT || 3606;
var db = 'mongodb://localhost/example';
expressOasGenerator.init(app, {});
var books = require('./routes/books');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use('/books', books);
app.listen(port, function(){
console.log('app listening on port: '+port);
});
Below is my schema:
var BookSchema = new Schema({
title: String,
author: String,
category: String,
booknames: [ String],
booktypes: [{
_id : false,
lang_code:{type : String ,required : true },
booktype: String,
}]
});
My api-spec looks like below:
paths: {
/books: {
get: {
summary: "/books",
consumes: [
"application/json"
],
parameters: [ ]
},
post: {
summary: "/books",
consumes: [
"application/json"
],
parameters: [ ]
}
},
My post api looks like below:
router.post('/', async(req, res, next)=>{
res.setHeader('Content-Type', 'application/json');
var newBook = new Book();
newBook.set(req.body)
await newBook.save(function(err, book){
if(err) {
res.status(HttpStatus.BAD_REQUEST)
.send({
error: HttpStatus.getStatusText(HttpStatus.BAD_REQUEST)
});
}
});
next();
});
I have seen the similar issues and implemented in the similar way. Please help where I am doing wrong.
https://github.com/mpashkovskiy/express-oas-generator/issues/9
https://github.com/mpashkovskiy/express-oas-generator/issues/4
the header must be set to html in the serveApiDocs method like this:
app.use(packageInfo.baseUrlPath + '/' + swaggerUiServePath, swaggerUi.serve, (req, res) => {
res.setHeader('Content-Type', 'text/html');
swaggerUi.setup(patchSpec(predefinedSpec))(req, res);
});
They are shutting down travis-ci.org and thus all the projects have to migrate to travis-ci.com.
From official email:
We encourage you to migrate your existing repositories that are currently on travis-ci.org over to travis-ci.com as soon as possible, enabling you to identify any additional changes required to your code or configuration well in advance of the December 31st deadline. We’ve documented everything you need to know in order to migrate your repositories, and of course, you can reach out on the community forum for any additional help.
Whenever I make requests to new routes I have created they do not appear in the docs. Here is my app.js file. Did I miss order something?
var express = require("express");
const expressOasGenerator = require("express-oas-generator");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");
const fs = require("fs");
const mkdirp = require("mkdirp");
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
var app = express();
let openAPIFilePath = "./docs/api.json";
/** handle the responses */
if (process.env.NODE_ENV !== "production") {
/** work-around - https://github.com/mpashkovskiy/express-oas-generator/issues/51 */
mkdirp.sync(path.parse(openAPIFilePath).dir);
/** work-around - https://github.com/mpashkovskiy/express-oas-generator/issues/52 */
let predefinedSpec;
try {
predefinedSpec = JSON.parse(
fs.readFileSync(openAPIFilePath, { encoding: "utf-8" })
);
} catch (e) {
//
}
/** work-arounds done. Now handle responses - MUST be the FIRST middleware */
expressOasGenerator.handleResponses(app, {
specOutputPath: openAPIFilePath,
predefinedSpec: predefinedSpec ? () => predefinedSpec : undefined,
});
}
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));
app.use("/", indexRouter);
app.use("/users", usersRouter);
expressOasGenerator.handleRequests();
app.listen(3000);
module.exports = app;
Update:
fixed after changing predefinedSpec: predefinedSpec ? () => predefinedSpec : undefined
to just predefinedSpec: predefinedSpec ? predefinedSpec : undefined
I feel like it should just read the existing spec.json on file and "merge" it, I am using server.js
as an example.
Originally from https://github.com/sarpik/turbo-schedule/issues/45
Work-around:
+const fs = require('fs')
/** handle the responses */
if (process.env.NODE_ENV !== 'production') {
/** work-around - https://github.com/mpashkovskiy/express-oas-generator/issues/51 */
mkdirp.sync(path.parse(openAPIFilePath).dir);
+ /** work-around - https://github.com/mpashkovskiy/express-oas-generator/issues/52 */
+ let predefinedSpec;
+ try {
+ predefinedSpec = JSON.parse(
+ fs.readFileSync(openAPIFilePath, { encoding: 'utf-8' })
+ );
+ } catch (e) {
+ //
+ }
/** work-arounds done. Now handle responses - MUST be the FIRST middleware */
handleResponses(app, {
specOutputPath: openAPIFilePath,
writeIntervalMs: 0,
+ predefinedSpec: predefinedSpec ? () => predefinedSpec : undefined,
});
}
It would be great if we had a config as 3rd param, where we can specify various settings, one of them being the path of the currently generated spec, so it can be loaded and saved.
Right now we're using it as:
expressOasGenerator.init(expressApp, require('../api-docs/api-spec.json'))
which means we have to manually update it via copy-paste when it changes.
With the new proposal it would look similar to:
expressOasGenerator.init(expressApp, null, {
currentSpec: '../api-docs/api-spec.json', // auto loads it
autoSave: true // or it can be a `_.debounce` number, defaulting to 1000ms
)})
Hi man,
I'm trying to use this library but I have the following issue from the console
TypeError: Cannot read property 'forEach' of undefined
index.js:36
at getEndpoints (/Users/jdnichollsc/dev/enturnate/enturnate-api/node_modules/express-list-endpoints/src/index.js:36:9)
at /Users/jdnichollsc/dev/enturnate/enturnate-api/node_modules/express-list-endpoints/src/index.js:65:9
at Array.forEach (<anonymous>)
at getEndpoints (/Users/jdnichollsc/dev/enturnate/enturnate-api/node_modules/express-list-endpoints/src/index.js:36:9)
at init (/Users/jdnichollsc/dev/enturnate/enturnate-api/node_modules/express-oas-generator/index.js:37:21)
at Timeout.setTimeout [as _onTimeout] (/Users/jdnichollsc/dev/enturnate/enturnate-api/node_modules/express-oas-generator/index.js:166:5)
at ontimeout (timers.js:475:11)
at tryOnTimeout (timers.js:310:5)
at Timer.listOnTimeout (timers.js:270:5)
And the app
value is:
[[FunctionLocation]]:internal#location
[[Scopes]]:Scopes[3]
arguments:TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
caller:TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
length:3
name:""
Let me know what you think
Thanks in advance, Nicholls
Is there plans in the roadmap to add generation of the open api 3 spec?
At least I don't know how to do it.
Your work is great! Saved a lot of time for my documentation, but the API request body params.
I use postman to run tests.
It would be great if the user could manipulate example
, before these are added to the spec.
The rationale is that in staging (or production!) mode, the examples that get accumulated might be too big, or too sensitive or otherwise useless and there is no standard way for the library to deal with such issues.
This can be achieved by passing a manipulateExample
callback option on init - for example this would trim large arrays, keeping only the first item:
expressOasGenerator.init(expressApp, null, {
manipulateExample: (example, path, method, statusCode, anythingElseHere?) => {
if (_.isArray(example) { // trim large example array
example = [ example[0] ];
if (example[0].secretCompanyKey)
example[0].secretCompanyKey = 'SOME_SECRET_KEY;
}
return example;
}
)})
Hi,
i am working with swagger for node api documentation with express-oas-generator
my main problem as every thing working for me until api call
i made api call its giving response it also updated in swagger.json file but at a run time the response is not shown in swagger ui
after clicking execute button i got response but not update in swagger ui
"express-oas-generator": "^1.0.7",
"swagger-ui-express": "^4.0.6",
While creating an example usage project with Jest, I ran into the same error I've had in my own project - the last request seems to be ignored.
Current work-around - create one more dummy request, so that the last request's information will get processed / written.
We'll need to fix this though.
I was trying to implement the new functionality, provided by #39.
It wouldn't work.
I found out what was wrong, here's a quick patch (solves the issue, but unfinished yet): I thought this solved the issue, but it did not. I'm a little lost now.
diff --git a/node_modules/express-oas-generator/index.js b/node_modules/express-oas-generator/index.js
index 753ce13..4d5a3ec 100644
--- a/node_modules/express-oas-generator/index.js
+++ b/node_modules/express-oas-generator/index.js
@@ -276,7 +276,7 @@ function handleResponses(expressApp, options = { pathToOutputFile: undefined, wr
*
* @returns void
*/
-function handleRequests(options = { path: 'api-docs', predefinedSpec: {} }) {
+function handleRequests(expressApp, options = { path: 'api-docs', predefinedSpec: {} }) {
/** make sure the middleware placement order (by the user) is correct */
if (responseMiddlewareHasBeenApplied !== true) {
throw new Error(WRONG_MIDDLEWARE_ORDER_ERROR);
@@ -286,7 +286,7 @@ function handleRequests(options = { path: 'api-docs', predefinedSpec: {} }) {
responseMiddlewareHasBeenApplied = false;
/** middleware to handle REQUESTS */
- app.use((req, res, next) => {
+ expressApp.use((req, res, next) => {
try {
const methodAndPathKey = getMethod(req);
if (methodAndPathKey && methodAndPathKey.method && methodAndPathKey.pathKey) {
What was the problem is that I thought that the global app
variable would hold a reference to the app
we pass in from the first handler - handleResponses
.
Thus I was not asking for the app
variable in the second handler - handleRequests
and just used the global one.
That did not work.
As seen in the patch above - after receiving the expressApp
variable inside the handleRequests
function -- everything worked fine.
My question is -- do we need the global app
variable?
if yes, then how would we go about it at the handleRequests
handler - override the global app
variable with the passed in expressApp
variable?
(That would probably make sense because it's the latest one), but do we need the global variable anyway then? (I haven't looked if it's needed for anything else yet - sorry:D)
I currently cannot get the new setup to work, so that needs something done too.
also, we currently are NOT passing in the app
variable into handleRequests
from the init
function - and it still works fine.
I don't know what's up.
Also, currently the docs state that you need to pass in the app
variable into the handleRequests
handler - that's not true, because it accepts only an options
object. We'll update that later too.
when implementing this tool i have noticed that request bodies are not captured.
can someone shed some light as to why this might be the case ?
Hello,
It is a great tool, but when documenting a Post it is not displaying the payload in the swager doc. How can I do that?
I used to have route handlers like this:
router.get("/", async (_req, res) => {
try {
return res.json({ foo: "bar" });
} catch (err) {
console.error(err);
return res.status(500).json({ foo: "", error: err });
}
});
and after reading #24 and #29, I've started using next
:
-router.get("/", async (_req, res) => {
+router.get("/", async (_req, res, next) => {
try {
- return res.json({ foo: "bar" });
+ res.json({ foo: "bar" });
+ return next();
} catch (err) {
console.error(err);
- return res.status(500).json({ foo: "", error: err });
+ res.status(500).json({ foo: "", error: err });
+ return next(err);
}
});
This seems to work fine in development, but when using the API in production, I get errors like this:
Error: Can't set headers after they are sent.
at SendStream.headersAlreadySent (/home/kipras/projects/turbo-schedule/node_modules/send/index.js:390:13)
at SendStream.send (/home/kipras/projects/turbo-schedule/node_modules/send/index.js:617:10)
at onstat (/home/kipras/projects/turbo-schedule/node_modules/send/index.js:729:10)
at FSReqCallback.oncomplete (fs.js:159:5)
If I revert back to how I was using the route handlers without calling next
OR only calling next if the response body/headers were NOT modified, everything works fine & I don't get the errors anymore.
This answer provides some useful information: https://stackoverflow.com/a/7789131
also, from the same thread: https://stackoverflow.com/a/7086621
What solves the issue is only calling next()
when the NODE_ENV !== "production", which makes the code look terrible and I'd love to avoid this:D
router.get("/", async (_req, res, next) => {
try {
res.json({ foo: "bar" });
+ if (process.env.NODE_ENV !== "production") {
- return next();
+ return next();
+ }
+
+ return;
} catch (err) {
console.error(err);
res.status(500).json({ foo: "", error: err });
+ if (process.env.NODE_ENV !== "production") {
- return next(err);
+ return next(err);
+ }
+
+ return;
}
Is there possibly any way to work around this?
I only really use the API spec generation once building:
I start up the server,
init expressOasGenerator,
activate API routes so that the docs get generated
& stop the server.
Then, once I have the openAPI.json file, I serve it in production through another package from npm (I use redoc, one could also use swagger-ui), because the file is just static, it does not need updating etc.
TL;DR:
The errors shouldn't be there. The next()
also should not be called in these places, as noted in the stackoverflow threads above.
Working around with process.env.NODE_ENV
works, but it's just wrong and I don't want to have my stuff like this.
Once again, I'd love to solve this if possible.
Sometimes my REST API fails with this error:
/usr/src/bff/node_modules/express-oas-generator/lib/processors.js:122
const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
^
SyntaxError: Unexpected token in JSON at position 0
at JSON.parse (<anonymous>)
at updateResponses (/usr/src/bff/node_modules/express-oas-generator/lib/processors.js:122:21)
at ServerResponse.res.end (/usr/src/bff/node_modules/express-oas-generator/lib/processors.js:156:9)
at Gzip.onStreamEnd (/usr/src/bff/node_modules/compression/index.js:212:14)
at emitNone (events.js:111:20)
at Gzip.emit (events.js:208:7)
at endReadableNT (_stream_readable.js:1064:12)
at _combinedTickCallback (internal/process/next_tick.js:138:11)
at process._tickCallback (internal/process/next_tick.js:180:9)
npm ERR! code ELIFECYCLE
I have not understood how to reproduce it yet, but may be a trim and try-catch would be helpful here.
Hi,
I have embedded your code in my NodeJS App (server.js)
When I open swagger-ui , I can see the routes , but when I try to execute the call it doesn't display the response .. (in browser network tab the call is successful and response is received)
Also attaching swagger file which is generated (changed extensions while attaching)
Thanks,
Aniket.
currently it's manually put as /api-docs
, would be nice to be able to provide a param to rename it to /docs
for example
Thanks for this awesome module.
Is there a way to not generate host
and schemes
in the spec?
We are trying to generate the spec during the build/test phase, package and use it in all environments. But because of the generated host
value, swagger does not seem to pick the url dynamically (based on the environment url where it is served from).
I tried the following for removing the host
but i still get the host generated as localhost:8080
.
generator.handleResponses(app, {
predefinedSpec: spec => {
_.set(spec, "info.title", "Test API");
_.set(spec, "info.description", "Test Description");
_.set(spec, "host", "");
return spec;
},
writeIntervalMs: 3000,
specOutputPath: "./server/swagger_admin.json"
});
I noticed this project doesn't have a license yet.
I've been using https://choosealicense.com/ for a while now and it's great for choosing an OSS license.
For the best availability, I think that the MIT License would be the most appropriate, just like express itself.
Though you can explore the licenses here: https://choosealicense.com/licenses/, or if you want in depth - here: https://choosealicense.com/appendix/. I'd still recommend MIT though:)
I can create a PR.
Edit:
It seems like you've put the ISC license inside package.json
- that's great, but I think that you need to place the license's file inside the repository too.
First of all thank for this library, it will be amazing if it works for us (tried 0.1.16 & 0.1.18).
So we get no endpoint generated, initially we thought because of if (req.url.startsWith('/api-')) {
(and ours does start with /api-
) so we comment it out. Still no results.
Next, we noticed that the line const pathKeys = Object.keys(spec.paths);
always produces an empty array initially, so the getPathKey
function always returns undefined and no endpoints get documented.
Could you please help us with this?
During npm start
.
I get:
..../node_modules/express-oas-generator/index.js:44
let path = prefix + route.route.path;
^
TypeError: Cannot read property 'path' of undefined
at stack.forEach.route (..../node_modules/express-oas-generator/index.js:44:39)
at Array.forEach (<anonymous>)
at app._router.stack.forEach.router (..../node_modules/express-oas-generator/index.js:42:11)
at Array.forEach (<anonymous>)
at init (..../node_modules/express-oas-generator/index.js:34:21)
at Timeout.setTimeout [as _onTimeout] (..../node_modules/express-oas-generator/index.js:162:5)
at ontimeout (timers.js:458:11)
at tryOnTimeout (timers.js:296:5)
at Timer.listOnTimeout (timers.js:259:5)
Big warning when process.env.NODE_ENV === 'production'.
Even not applying the middlewares.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.