Code Monkey home page Code Monkey logo

specmatic-node's Introduction

Specmatic Framework Node Module

tests publish release

This node module is a thin wrapper over the specmatic executable jar. All core capabilities are in the main Specmatic project. The purpose of this wrapper module is to act as a helper with below aspects.

  • Easy installation and upgrade of the jar file in node projects through npm
  • Global install using npm and easy run specmatic jar executable without having to download the jar file and having to run java -jar
  • Programmatic access to some of the specmatic options as api like start / stop the stub server, setting expecations, running tests. These helpers methods can be used in a javascript project programmatically instead of using cli scripts.

Table Of Contents

Quick Start

npm install specmatic will install the specmatic locally to the node project.

Sample npm scripts to run specmatic, (Check Documentation for more information on cli commands and arguments.)

Contract as Stub / Smart Mock (For API clients / consumers)

In stub mode, Specmatic emulates the Provider / API / Service based on the API Specification so that the consumer / client application can make independent progress. Learn more.

Contract as Test (For API Providers / Service)

Tests for Free – Specmatic parses your API Specification files and based on this generates requests which are fired at your application. It then verifies if your application’s response is as per your API Specification. All this with a “No Code” approach.. Learn More

Sample Repo

https://github.com/znsio/specmatic-order-bff-nodejs

Programmatic Access

Specmatic JS library exposes some of the commands as methods that can be run programmatically from any javascript testing framework, during setup or test phases.

import {
    startHttpStub,
    setHttpStubExpectationJson,
    setHttpStubExpectations,
    stopHttpStub,
    test,
    showTestResults,
    printJarVersion,
    startKafkaMock,
    setKafkaMockExpecations,
    stopKafkaMock,
    verifyKafkaMock
} from 'specmatic';

Core APIs

startHttpStub(host?: string, port?: number, args?: (string | number)[]): Promise<Stub>
Start the stub server. Argument args values are passed directly to specmatic jar executable.
Note: This replaces startStub method which is deprecated

setHttpStubExpectationJson(stubResponse: any, stubServerBaseUrl?: string): Promise<boolean>
Set stub expectation from a static JSON object. Stub should be running before invoking this method.

setHttpStubExpectations(stubPath: string, stubServerBaseUrl?: string): Promise<boolean>
Set stub expectation from a file. Stub should be running before invoking this method.
Note: This replaces setExpectations method which is deprecated

stopHttpStub(stub: Stub)
Stop the stub server
Note: This replaces stopStub method which is deprecated

test(host?: string, port?: string, contractPath?: string, args?: (string | number)[]): Promise<{ [k: string]: number } | undefined>
Run tests. Argument args values are passed directly to specmatic jar executable.

showTestResults(testFn: (name: string, cb: () => void) => void)
View test results in any framework so that it shows up in IDE specific test results interface. Refer IDE Support below for details on how to use this feature.

printJarVersion()
method to print the version of specmatic.jar

enableApiCoverage(expressAppRef)
enable api coverage for express apps to know which apis and http verbs are covered in contract tests and which not

Kafka APIs

startKafkaMock(port?: number, args?: (string | number)[]): Promise<KafkaStub>
Start kafka stub. Requires an OpenAPI kafka spec in specmatic.json.
Note: This replaces startKafkaStub method which is deprecated

setKafkaMockExpectations(stub: KafkaStub, expecations: any): Promise<void>
Set expected message count on Kafka for each topic. Expecations are of the format

[
    {
        "topic": "product-queries",
        "count": 2
    },
    {
        "topic": "test-topic",
        "count": 2
    }
]

Note: This replaces setKafkaStubExpectations method which is deprecated

stopKafkaMock(stub: KafkaStub)
Stop a running kafka stub.
Note: This replaces stopKafkaStub method which is deprecated

verifyKafkaMock(stub: KafkaStub): Promise<Boolean>
Verify all expecations set on Kafka.
Note: This replaces verifyKafkaStub method which is deprecated

verifyKafkaMockMessage(stub: KafkaStub, topic: string, value: string): Promise<Boolean>
Verify kafka message. This is invoked in tests to check on kafka side if a message expected to by pushed by a BFF api is recieved by Kafka. The Kafka stub starts a verification end point for this purpose which is invoked internally by this api..
Note: This replaces verifyKafkaStubMessage method which is deprecated

IDE Support

Specmatic tests can be displayed in IDE specific test result view by using showTestResults method coupled with test method. Test framework specific steps are below.

Jest Framework

Example: https://github.com/znsio/specmatic-order-bff-nodejs/blob/main/test/contract

  1. Call test method in a globalSetup script. globalSetup script path can be set either in the jest command line argument or in jest configuration file.
  2. Call showTestResults in the root of your test file anywhere. You can pass test method of Jest as its argument and it works out of the box.

Note 1: Since you are running test method in a globalSetup script, any pre-test setup like starting a stub server, app server and any dependent processes like redis server has to be done in the globalSetup script in required sequence before test method is called.

Note 2: If your project already has a jest globalSetup and or globalTeardown scripts then reuse them but include the necessary code to make IDE integration work.

Note 3: If your project uses jest projects support (--projects), then configure globalSetup/globalTeardown in the project specific jest config file

Logging

By default only warning and error messages are displayed. You can configure the loglevel in package.json as

"specmatic": {
    "logLevel": "debug"
},

logLevel accepts all values supported by winston logger

Known Issues

1. Node 17/18 - Connection Refused error when connecting to stub

Node 18 apparently shifted to IPv6 as first choice for resolving hostname when both IPv4 and IPv6 addresses are available. This means localhost most likely resolves to ::1 rather than 127.0.0.1 or 0.0.0.0. Now specmatic node wrapper does not start the stub server but the java program under the hood does it and java still resolves to IPv4 address by default. Thus localhost on node v18 and java might resolve to a different address and any connection from node to the running stub will fail. To resolve this, until we have a permanent solution, we request to disable any IPv6 address mapping to a named host in your DNS resolver or /etc/hosts.

2. Error "ReferenceError: setImmediate is not defined"

This happens due to an issue in Jest framework. The easiest solution is to import core-js in the affected test file.

3. Specmatic stub is not terminated after test execution

This happens if stub is not stopped in the same way it is started. There can be two possibilities in case of Jest framework

  1. If started from before* methods in a test suite, then it should be stopped using stopStub method in corresponding after* method
  2. If started using globalSetup script, then it should be stopped in a globalTeardown script

Note: If bail is set to true in jest config, then any test failure will abort further execution of tests including after* methods and globalTeardown script. This will prevent stopping your stubs and other processes leaving them hanging and causing port conflicts when tests are run again next.

4. Test results don't show up in IDE

We have tested IDE integration with webstorm and jest framework combination. Visual Studio Code seems to work on and off with Jest. Please follow the instructions mentioned in IDE Support to set this up.

Any other test framework can easily be also configured to display test results in IDE test results view by passing a convertor function to the showTestResults api.

Contribution

Please refer to this link

specmatic-node's People

Contributors

anuraggrover avatar burdeamit avatar dependabot[bot] avatar deveshch avatar harikrishnan83 avatar jaydeepk avatar joelrosario avatar nashjain avatar sahilaug avatar sarveshsharma101 avatar starkhan6368 avatar tkrisztian95 avatar vikram-rao avatar yogeshnikam671 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

specmatic-node's Issues

Error executing git rev-list --count HEAD..@{u}: fatal: HEAD does not point to a branch

Describe the bug
I try to run a single contract test on the consumer side by npm run test -- service.test.ts command and I get this error Error executing git rev-list --count HEAD..@{u}: fatal: HEAD does not point to a branch always when I have the .specmatic folder exists. It has the cloned provider service repository under .specmatic/repos (also containing the api specificiation/contract yaml as well).

Console log

$ npx npm run test -- service.test.ts
Debugger attached.
Debugger attached.

> @some/[email protected] test
> vitest --run --root src/ service.test.ts

Debugger attached.

 RUN  v0.28.5 /mnt/c/Git/GitLab/poc-specmatic-contract-test/ui/main-component/src

stdout | unknown test
[specmatic] 13-12-2023 15:15:48 info: Stub: Starting server
[specmatic] 13-12-2023 15:15:48 debug: Stub: Executing "stub --host=127.0.0.1 --port=10002"
[specmatic] 13-12-2023 15:15:48 debug: CLI: Specmatic jar path: /mnt/c/Git/GitLab/poc-specmatic-contract-test/ui/node_modules/specmatic/specmatic.jar

stdout | unknown test
[specmatic] 13-12-2023 15:15:51 debug: Stub: Loading config file ./specmatic.json


stdout | unknown test
[specmatic] 13-12-2023 15:15:51 debug: Stub: Looking for a contract repo checkout at: /mnt/c/Git/GitLab/poc-specmatic-contract-test/ui/main-component/.specmatic/repos/service


stdout | unknown test
[specmatic] 13-12-2023 15:15:53 debug: Stub: Error executing git rev-list --count HEAD..@{u}: fatal: HEAD does not point to a branch


 ❯ utils/service/tests/contract/service.test.ts (1)
   ❯ Start Service stubbed by Specmatic for contract tests (1)
     ⠴ [ beforeAll ]
     · Try fetch Service Template List Success

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
Running tests again should check if the repo with the contract is already there. Somehow skip this step or help me what is possibly wrong with my Git.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Windows 10 WSL Ubuntu
  • Version: "^1.0.3" (package.json)

Additional context
If I deleted the .specmatic folder manually after a failed test run this problem seems to solved. It clones the repo again and starts the stub http server.

Programmatic support for specmatic install command

Is your feature request related to a problem? Please describe.
Need programmatic support to install specmatics in the local environment which will checkout all the specmatics from the repositories given in the specmatic.json. CLI support for specmatic install works but the programmatic version would be needed to be able to write cross-platform script.

Describe the solution you'd like
A simple function that exposes specmatics install command.

Describe alternatives you've considered
None.

Additional context
This may be required to write cross-platform scripts.

Setting expecation failed with error AxiosError: Request failed with status code 400

Describe the bug
Using the await setHttpStubExpectations("some file here...", httpStub.url); i got this error about setting expectations failed and the Specmatic stub server returns with a 400 Bad request.

Snippet from actual test code:

const HTTP_STUB_HOST = "127.0.0.1";
const HTTP_STUB_PORT = 10002;
const TEST_TIMEOUT_MS = 10 * 60 * 1000; //10 mins CI needs a long timeout

describe("Start Service stubbed by Specmatic for contract tests", () => {
  let httpStub;

  beforeAll(async () => {
    httpStub = await startHttpStub(HTTP_STUB_HOST, HTTP_STUB_PORT);
    await setHttpStubExpectations("./templates.json", httpStub.url);
  }, TEST_TIMEOUT_MS);

Debug log:

[specmatic] 13-12-2023 14:40:32 info: Stub: Stub server is running on http://127.0.0.1:10002. Ctrl + C to stop.

[specmatic] 13-12-2023 14:40:32 info: Set Expectations: Stub url is http://127.0.0.1:10002

stdout | unknown test
[specmatic] 13-12-2023 14:40:33 debug: Stub: 
--------------------
  Request at 2023-12-13 2:40:33.211
    POST /_specmatic/expectations
    Accept: application/json, text/plain, */*
    Content-Type: application/json
    Referer: http://localhost:3000/
    User-Agent: Mozilla/5.0 (linux) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.7.0
    Accept-Language: en
    Origin: http://localhost:3000
    Content-Length: 1932
    Accept-Encoding: gzip, deflate
    Host: 127.0.0.1:10002
    Connection: keep-alive
    
    {
        "http-request": {
            "path": "some api path is here...",
            "method": "GET",
            "headers": {
                "Authorization": "Bearer some token is here..."
            }
        },
        "http-response": {
            "status": 200,
            "body": [
                {
                  // some json object is here...
                }
            ],
            "status-text": "OK"
        }
    }

  Response at 2023-12-13 2:40:33.396
    400 Bad Request
    X-Specmatic-Result: failure
    
    No match was found.



stdout | unknown test
[specmatic] 13-12-2023 14:40:33 error: Set Expectations: Failed with error AxiosError: Request failed with status code 400
[specmatic] 13-12-2023 14:40:33 debug: Stub: Stopping server at http://127.0.0.1:10002
[specmatic] 13-12-2023 14:40:33 debug: Trying to stop stub process gracefully ...

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior

Some information about why this cannot set/upload the expectations.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Windows 10 WSL Ubuntu
  • ENV: Locally and CI pipeline too
  • Version: "^1.0.3" (NPM package.json)

Additional context

When I start stub by the Specmatic CLI eg specmatic stub service.yaml it works with the same json file (canned response) from the service_data folder.

Empty collection can't be reduced error when calling API on stub http server

Description
I have an API specification with one API path that is returning a JSON array of objects. I don't have a template for response and when a request from test hit the stub server it causes a 500 error Empty collection can't be reduced.

API specification snippet service.yaml

---
openapi: "3.0.1"
info:
  title: "Contract for the AI service"
  version: "1"
paths:
  /service/api/v1/templates/list:
    get:
      summary: "Should be able to get templates"
      parameters: # Workaround for local testing https://github.com/znsio/specmatic/issues/577
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
            enum:
              [
                "Bearer some token is here...",
              ]
      responses:
        "200":
          description: "An array of templates"
          content:
            application/json:
              schema: 
                $ref: '#/components/schemas/ArrayOfTemplates'
              examples:
                FETCH_TEMPLATES_SUCCESS:
                  value:
                  - uuid: 16c47839-da6d-4c8d-9c66-5dcb8d8f52bf
                    displayName: My template
                    description: Here comes some text.
components:
  schemas:
    ArrayOfTemplates: # Schema name
      type: array
      items:
        $ref: '#/components/schemas/Template'
    Template: # Schema name
      type: object
      required:
        - "uuid"
        - "displayName"
      properties:
        uuid:
          type: string
          format: uuid
        displayName:
          type: string

Console output:

[specmatic] 13-12-2023 16:09:23 debug: Stub: 
--------------------
  Request at 2023-12-13 4:9:23.556
    GET /service/api/v1/templates/list
    host: 127.0.0.1:10002
    connection: keep-alive
    accept: application/json
    content-type: application/json
    authorization: Bearer token
    accept-language: *
    sec-fetch-mode: cors
    user-agent: undici
    accept-encoding: gzip, deflate
    
    

  Response at 2023-12-13 4:9:23.581
    500 Internal Server Error
    X-Specmatic-Result: failure
    
    Empty collection can't be reduced
    
    java.lang.UnsupportedOperationException: Empty collection can't be reduced.
        at in.specmatic.stub.HttpStubKt.fakeHttpResponse(HttpStub.kt:1037)
        at in.specmatic.stub.HttpStubKt.getHttpResponse(HttpStub.kt:648)
        at in.specmatic.stub.HttpStub.serveStubResponse(HttpStub.kt:312)
        at in.specmatic.stub.HttpStub.access$serveStubResponse(HttpStub.kt:41)
        at in.specmatic.stub.HttpStub$environment$1$1$2.invokeSuspend(HttpStub.kt:170)
        at in.specmatic.stub.HttpStub$environment$1$1$2.invoke(HttpStub.kt)
        at in.specmatic.stub.HttpStub$environment$1$1$2.invoke(HttpStub.kt)
        at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:120)
        at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:78)
        at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invokeSuspend(BaseApplicationEngine.kt:124)
        at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invoke(BaseApplicationEngine.kt)
        at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invoke(BaseApplicationEngine.kt)
        at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:120)
        at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:78)
        at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:98)
        at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
        at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invokeSuspend(Pipeline.kt:478)
        at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
        at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
        at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:17)
        at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invokeSuspend(DefaultEnginePipeline.kt:123)
        at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invoke(DefaultEnginePipeline.kt)
        at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invoke(DefaultEnginePipeline.kt)
        at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:120)
        at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:78)
        at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:98)
        at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
        at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invokeSuspend(Pipeline.kt:478)
        at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
        at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
        at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:17)
        at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:119)
        at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
        at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:44)
        at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:112)
        at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
        at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
        at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:37)
        at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:29)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:61)
        at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:425)
        at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.ktor.server.netty.EventLoopGroupProxy$Companion.create$lambda$1$lambda$0(NettyApplicationEngine.kt:296)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)

Steps to reproduce

  1. Create a file ...
  2. Run this command: ...
  3. Make an http request looking like this: ...

Expected behavior
Returning 200 status code and json array with randomized JSON object.

Screenshots
If applicable, add screenshots to help explain your problem.

System Information:

  • OS & version: Windows 10 WSL Ubuntu
  • Specmatic version: "^1.0.3" (package.json)
  • JDK version: 17

Additional context
I'm running the test from NodeJS test and it starts the http stub server. Because of #111 i cannot set/provide a canned response from a template.json. If I start the stub server from CLI by specmatic stub service.yaml without the service_data and not from the tests it works as expected and return randomized data.

Error import Specmatic in Cypress tests

Describe the bug
I'm trying to import Specmatic in a Cypress test, like

import { setHttpStubExpectations } from "specmatic";

and I'm getting the following error while running the tests by npx cypress run --browser electron:

TypeError: The following error originated from your test code, not from Cypress.   > _fs.default.existsSync is not a function

Desktop:

  • OS: Windows 10
  • Node version: 18.12.
  • Specmatic Version: 1.2.14
  • "cypress": "^13.2.0",

Additional context
I'm trying to achieve that from a Cypress test, using the cy.intercept() and cy.reply() functions, the tests are backed with the Specmatic stub and it returns the example/transient stubs always verified against the API specification.

For example:

    cy.intercept("/api/v1/foo/list", async (req) => {
      // fetch test data from Specmatic stub
      const data = await fetch("http://localhost:35337" + req.url, {
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
          Authorization: "Bearer token"
        }
      });
      req.reply(data);
    });

Should this work?

Ability to dynamically send time parameters in the request ,for creating meeting, startTIme endtime etc which are the details in the examples

Is your feature request related to a problem? Please describe.
The API, that I am working on requires me to get the meetingId of a meeting that has started and not expired at the same time.
It should be an ongoing meeting. Hence having predefined meeting details already in the database doesn't work as it gets expired after some time

Describe the solution you'd like
Being able to dynamically create a meeting by calling the respective spec file and sending parameters as request body, to the meeting contract test. As a result, create the meeting with the desired start and end time.

Additional context

  • If the meeting is not started the API throws an error as "Meeting not started yet"

  • 1

  • If the meeting has ended the API throws an error as "Meeting has been expired"

  • 2

Set http stub expectation from static object

Is your feature request related to a problem? Please describe.
The function setHttpStubExpectations has a stubPath parameter.

Describe the solution you'd like
I would like to setup the mock and define request/response from code, without writing it to a json file.

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.