Giter VIP home page Giter VIP logo

fastify-session's Introduction

FastifySession

npm version npm total downloads npm monthly downloads npm license
build status dependencies status

Features

Session plugin for fastify that supports both stateless and sateful sessions.

Install

npm install @mgcrea/fastify-session @fastify/cookie
# or
pnpm add @mgcrea/fastify-session @fastify/cookie

Quickstart

Basic example (signed session with hmac stored in a volatile in-memory store)

Defaults to a volatile in-memory store for sessions (great for tests), with hmac for signature.

import createFastify, { FastifyInstance, FastifyServerOptions } from "fastify";
import fastifyCookie from "@fastify/cookie";
import fastifySession from "@mgcrea/fastify-session";

const SESSION_SECRET = "a secret with minimum length of 32 characters";
const SESSION_TTL = 86400; // 1 day in seconds

export const buildFastify = (options?: FastifyServerOptions): FastifyInstance => {
  const fastify = createFastify(options);

  fastify.register(fastifyCookie);
  fastify.register(fastifySession, {
    secret: SESSION_SECRET,
    cookie: { maxAge: SESSION_TTL },
  });

  return fastify;
};

Production example (signed session with sodium stored in redis)

For better performance/security, you can use the @mgcrea/fastify-session-sodium-crypto addon:

Leveraging an external redis store, the session id (generated with nanoid) is signed using a secret-key with libsodium's crytpo_auth

import createFastify, { FastifyInstance, FastifyServerOptions } from 'fastify';
import fastifyCookie from 'fastify-cookie';
import fastifySession from '@mgcrea/fastify-session';
import { SODIUM_AUTH } from '@mgcrea/fastify-session-sodium-crypto';

const SESSION_KEY = 'Egb/g4RUumlD2YhWYfeDlm5MddajSjGEBhm0OW+yo9s='';
const SESSION_TTL = 86400; // 1 day in seconds
const REDIS_URI = process.env.REDIS_URI || 'redis://localhost:6379/1';

export const buildFastify = (options?: FastifyServerOptions): FastifyInstance => {
  const fastify = createFastify(options);

  fastify.register(fastifyCookie);
  fastify.register(fastifySession, {
    key: Buffer.from(SESSION_KEY, 'base64'),
    crypto: SODIUM_AUTH,
    store: new RedisStore({ client: new Redis(REDIS_URI), ttl: SESSION_TTL }),
    cookie: { maxAge: SESSION_TTL },
  });

  return fastify;
};

Stateless example (encrypted session with sodium not using a store)

No external store required, the entire session data is encrypted using a secret-key with libsodium's crypto_secretbox_easy

Here we used a secret instead of providing a key, key derivation will happen automatically on startup.

import createFastify, { FastifyInstance, FastifyServerOptions } from "fastify";
import fastifyCookie from "fastify-cookie";
import fastifySession from "@mgcrea/fastify-session";
import { SODIUM_SECRETBOX } from "@mgcrea/fastify-session-sodium-crypto";

const SESSION_TTL = 86400; // 1 day in seconds

export const buildFastify = (options?: FastifyServerOptions): FastifyInstance => {
  const fastify = createFastify(options);

  fastify.register(fastifyCookie);
  fastify.register(fastifySession, {
    secret: "a secret with minimum length of 32 characters",
    crypto: SODIUM_SECRETBOX,
    cookie: { maxAge: SESSION_TTL },
  });

  return fastify;
};

Benchmarks

Session crypto sealing

node --experimental-specifier-resolution=node --loader=ts-paths-esm-loader/transpile-only --no-warnings test/benchmark/cryptoSeal.ts
SODIUM_SECRETBOX#sealJson x 333,747 ops/sec ±0.62% (91 runs sampled)
SODIUM_AUTH#sealJson x 376,300 ops/sec ±0.50% (89 runs sampled)
HMAC#sealJson x 264,292 ops/sec ±3.13% (85 runs sampled)
Fastest is SODIUM_AUTH#sealJson

Session crypto unsealing

node --experimental-specifier-resolution=node --loader=ts-paths-esm-loader/transpile-only --no-warnings test/benchmark/cryptoUnseal.ts
SODIUM_SECRETBOX#unsealJson x 424,297 ops/sec ±0.69% (86 runs sampled)
SODIUM_AUTH#unsealJson x 314,736 ops/sec ±0.96% (89 runs sampled)
HMAC#unsealJson x 145,037 ops/sec ±5.67% (78 runs sampled)
Fastest is SODIUM_SECRETBOX#unsealJson

Authors

Credits

Heavily inspired from

License

The MIT License

Copyright (c) 2020 Olivier Louvignes <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

fastify-session's People

Contributors

fa-sharp avatar i0x0 avatar mgcrea avatar oof2win2 avatar qwertyforce 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

Watchers

 avatar  avatar  avatar

fastify-session's Issues

Update log level to debug

Could these error entries be changed to debug?

These are business as usual operations. I wouldn't expect them to be surfaced in info because they don't have an actionable intel.

As it is, it is flooding our log system with very low value logs.

How to add custom data to the session?

For example, while I was using express-session or the other fastify-session plugin, i was able to add custom data by doing req.session.userId='some id' after extending the request object.

I've been trying similar process here by doing something like this but the custom session data isn't getting saved. (as commented in this file - https://github.com/mgcrea/fastify-session/blob/master/src/session/SessionData.ts)

declare module "fastify" {
  interface FastifyRequest {
    session: Session;
  }
}

declare module "@mgcrea/fastify-session" {
  interface SessionData {
    userId: string;
  }
}

Adding it like this -

    await app.register(fastifyCookie);
    await app.register(fastifySession, {
      key: Buffer.from(SESSION_KEY, "base64"),
      crypto: SODIUM_AUTH,
      store: new RedisStore({ client: new Redis(REDIS_URI), ttl: SESSION_TTL }),
      cookie: { maxAge: SESSION_TTL }
    });

then using it here -

    app.get("/set", async (request: FastifyRequest) => {
      request.session.data.userId = "1";
      const id = request.session.data.userId;
      console.log("ID", id);
      return {
        id,
      };
    });

    app.get("/get", async (request: FastifyRequest) => {
      const id = request.session.data.userId;
      return {
        id,
      };
    });

And the custom data that i set does not stay persistent. I've looked into the source code but I'm not exactly able to figure out this issue. The sessionId remains same in both request when i console them.

Thanks.

FastifyPluginAsync is not assignable to type FastifyPluginCallback

Hi, I tried to troubleshoot an issue where every change to session data causes new session identifier to be issued regardless of using in-memory or your redis-store, so I cloned and locally build your repo, linked to my test repo and when I try to compile my typescript code I'm getting:

error TS2769: No overload matches this call.
Type 'FastifyPluginAsync<FastifySessionOptions, Server>' is not assignable to type 'FastifyPluginCallback<FastifySessionOptions, Server>'.

Eslint complains here:

fastify.register(FastifySession, {
                 ~~~~~~~~~~~~~~

Do you have any idea what might be the cause of either of these problems?

req.session is undefined

Hey! @mgcrea Might you help me, please! I'm trying to use your package to implement sessions with redis store in my current project , and I cant figure out what is going wrong, req.session is undefined(

Registering your sessions as plugin

import * as dotenv from 'dotenv'

import { FastifyPluginCallback } from 'fastify'
import fastifyCookie from 'fastify-cookie'
import fastifySession from '@mgcrea/fastify-session'
declare module '@mgcrea/fastify-session' {
  interface SessionData {
    userId: string
  }
}
import RedisStore from '@mgcrea/fastify-session-redis-store'
import Redis from 'ioredis'
import { SODIUM_AUTH } from '@mgcrea/fastify-session-sodium-crypto'

dotenv.config()

const ttl = 864e3 * 365 // 1 day in seconds
const secret = process.env.SESSION_SECRET

export const session: FastifyPluginCallback = (app, _, done) => {
  app.register(fastifyCookie)
  app.register(fastifySession, {
    key: Buffer.from(secret, 'base64'),
    crypto: SODIUM_AUTH,
    store: new RedisStore({
      client: new Redis({
        host: process.env.REDIS_HOST,
        port: Number(process.env.REDIS_PORT),
        password: process.env.REDIS_PASSWORD,
      }),
      ttl,
    }),
    cookie: { maxAge: ttl },
  })
  done()
}

and then use it with app

import fastify from 'fastify'
import auth from 'fastify-auth'
import { bodyParser, logger, session } from 'middleware'

import routes from 'routes'

const app = fastify({ logger })

app.register(bodyParser)
app.register(session)
app.register(auth)

app.register(routes)

app.get('/', async (req, res) => {
  console.log('req.session =', JSON.stringify(req.session))
  res.send('Helo code!')
})

app.listen(4000)

Routes and my self-made body parser working fine as plugins, but something wrong with sessions and I've got really tired of figuring out, I'm sorry, I need your help!

image

Trying to write own store

Hello, trying to write own store, but has an problem.

On each authentication my code creates 3 entities in DB.

On each page update my code creates 1 entities in DB.

What i did wrong?

import { SessionData, SessionStore } from "@mgcrea/fastify-session";
import { Session } from "@riddea/typeorm";
import { getRepository } from "typeorm";

export class TypeormStore<T extends SessionData = SessionData> implements SessionStore {
  private readonly repository = getRepository(Session)

  async get(sid: string): Promise<[T, number]> {
    try {
      const session = await this.repository.findOne({ sid })

      if (!session) {
        return null
      }

      if (session?.expiredAt <= Date.now()) {
        return null;
      }

      return [JSON.parse(session?.json ?? {}) as T, session?.expiredAt]
    } catch (error) {
      console.error(error)
      return [null, null]
    }
    
  }

  async set(sid: string, session: T, expiredAt: number | null = null): Promise<void> {
    try {
      await this.repository.save({ sid, json: JSON.stringify(session), expiredAt: expiredAt || Date.now() })
    } catch (error) {
      console.error(error)
    }
  }

  async destroy(sid: string): Promise<void> {
    await this.repository.delete({ sid })
  }

  async all(): Promise<T[]> {
    const sessions = await  this.repository.find()

    return sessions?.map(s => JSON.parse(s.json) as T) ?? []
  }

  length() {
    return this.repository.count()
  }

  clear() {
    return this.repository.clear()
  }

  async touch(sid: string, expiry?: number): Promise<void> {
    try {
      const sessionData = await this.get(sid);

      if (!sessionData || !sessionData?.length) {
        return;
      }

      const [session] = sessionData;

      await this.set(sid, session, expiry);
    } catch (error) {
      console.error(error)
    }
  }
}
app.register(fastifySession, {
    secret: process.env.WEB_SESSION_SECRET,
    saveUninitialized: false,
    cookie: {
      maxAge: 864e3, // 1 day
    },
    store: new TypeormStore()
  });

Any way to use fastify-session alongside fastify-passport?

Hey, currently the https://github.com/fastify/fastify-passport only support https://github.com/fastify/fastify-secure-session as the session manager, and I see there is this PR fastify/fastify-passport#93 by @mgcrea for enabling custom session stores, so until that PR is merged is there any way to use fastify-session alongside fastify-passport, so that I can use https://github.com/mgcrea/fastify-session-redis-store in order to persist the sessions?

Thanks.

Could you please share tsconfig you extend?

Hello there, comrade @mgcrea! I'd like to PR a feature to get session from querystring parameter like server/someRoute?Session=ID, and I think I should to use your tsconfig, but seems like you extending it from file in other directory :)

{
  "extends": "@tsconfig/node10/tsconfig.json",
  "compilerOptions": {
    "esModuleInterop": true,
    "declaration": true,
    "outDir": "./lib",
    "rootDir": "./src",
    "baseUrl": "./"
  },
  "include": ["src/**/*"]
}

Compatibility with express-session

Currently migrating from express-session to this package is pain because of incompatible APIs.

It would be nice if session values should be read / written the same way they are in express-session;

fastify-session (current):

session.set('someValue', 123);
session.get('someValue');

express-session (desired):

session.set('someValue', 123);
session.get('someValue');

Session.destroy() should work for stateless sessions as well

The session.destroy() function doesn't do anything when using stateless sessions, but it would make sense for it to unset the Cookie header in order to emulate the same behavior as stateful sessions. This way if I'm using stateless sessions in development mode, the cookie is still deleted when logging out the user.

This can be achieved by simply moving the this.deleted = true to the first line of the destroy function, to ensure the cookie is deleted before returning out of the function:

/**
* Delete the session. Only applicable for stateful sessions.
*/
async destroy(): Promise<void> {
if (Session.#sessionCrypto.stateless) {
return;
}
this.deleted = true;
// Only relay destroy to the store for existing sessions that have not been saved
if (this.created && !this.saved) {
return;
}
assert(Session.#sessionStore);
await Session.#sessionStore.destroy(this.id);
}

stateless session does recover the id back

Session {
  created: false,
  rotated: false,
  changed: false,
  deleted: false,
  id: 'KtYYfvwgFIOAewUhLOYcc',
  [Symbol(kSessionData)]: { from: 'code', id: 'QsIrNJYjISb48Ie3MknA4' },
  [Symbol(kCookieOptions)]: { maxAge: 86400, path: '/' }
}

KtYYfvwgFIOAewUhLOYcc is not QsIrNJYjISb48Ie3MknA4.

The problem is code here:

if (Session[kSessionCrypto].stateless) {
const session = new Session(JSON.parse(cleartext.toString()));
session.rotated = rotated;
return session;
}

The line 71, if we don't pass id to the constructor manually, then nanoid will generate another new one.

Possible solution:

--- a/node_modules/@mgcrea/fastify-session/lib/session/Session.js
+++ b/node_modules/@mgcrea/fastify-session/lib/session/Session.js
@@ -36,7 +36,8 @@ class Session {
         const { buffer: cleartext, rotated } = Session[exports.kSessionCrypto].unsealMessage(cookie, Session[exports.kSecretKeys]);
         // Stateless sessions have the whole session data encrypted as the cookie
         if (Session[exports.kSessionCrypto].stateless) {
-            const session = new Session(JSON.parse(cleartext.toString()));
+            const data = JSON.parse(cleartext.toString())
+            const session = new Session(data, {id: data.id})
             session.rotated = rotated;
             return session;
         }

Typescript config

Hi! Currently I am trying to implement this library to set a session based auth. However, I am struggling with the following error... What TSconfigs should I use?

{"level":50,"time":1676542402897,"pid":15040,"hostname”:”PC”,”err": { "type": "Error", "message":"require() of ES Module server/node_modules/nanoid/index.js from server/node_modules/@mgcrea/fastify-session/dist/index.cjs not supported. Instead change the require of index.js in server/node_modules/@mgcrea/fastify-session/dist/index.cjs to a dynamic import() which is available in all CommonJS modules.", "stack":"Error [ERR_REQUIRE_ESM]: require() of ES Module server/node_modules/nanoid/index.js from server/node_modules/@mgcrea/fastify-session/dist/index.cjs not supported.Instead change the require of index.js in server/node_modules/@mgcrea/fastify-session/dist/index.cjs to a dynamic import() which is available in all CommonJS modules.\n at require.extensions..jsx.require.extensions..js (/private/var/folders/17/syph5qn950scym0mh4fjg_9m0000gn/T/ts-node-dev-hook-847473055250209.js:114:20)\n at Object.nodeDevHook [as .js] (server/node_modules/ts-node-dev/lib/hook.js:63:13)\n at Object.<anonymous> (server/node_modules/@mgcrea/fastify-session/dist/index.cjs:121:21)\n at Module._compile (server/node_modules/source-map-support/source-map-support.js:568:25)\n at require.extensions..jsx.require.extensions..js (/private/var/folders/17/syph5qn950scym0mh4fjg_9m0000gn/T/ts-node-dev-hook-847473055250209.js:114:20)\n at Object.nodeDevHook [as .js] (server/node_modules/ts-node-dev/lib/hook.js:63:13)\n at Object.<anonymous> (server/src/plugins/setup-auth.ts:9:43)\n at Module._compile (server/node_modules/source-map-support/source-map-support.js:568:25)\n at m._compile (/private/var/folders/17/syph5qn950scym0mh4fjg_9m0000gn/T/ts-node-dev-hook-847473055250209.js:69:33)\n at require.extensions..jsx.require.extensions..js (/private/var/folders/17/syph5qn950scym0mh4fjg_9m0000gn/T/ts-node-dev-hook-847473055250209.js:114:20)\n at require.extensions.<computed> (/private/var/folders/17/syph5qn950scym0mh4fjg_9m0000gn/T/ts-node-dev-hook-847473055250209.js:71:20)\n at Object.nodeDevHook [as .ts] (server/node_modules/ts-node-dev/lib/hook.js:63:13)\n at loadPlugin (server/node_modules/@fastify/autoload/index.js:259:15)\n at server/node_modules/@fastify/autoload/index.js:43:12\n at Array.map (<anonymous>)\n at autoload (server/node_modules/@fastify/autoload/index.js:42:33)","code":"ERR_REQUIRE_ESM"},"msg":"require() of ES Module server/node_modules/nanoid/index.js from server/node_modules/@mgcrea/fastify-session/dist/index.cjs not supported.\nInstead change the require of index.js in server/node_modules/@mgcrea/fastify-session/dist/index.cjs to a dynamic import() which is available in all CommonJS modules."}

Here is my tsconfig file

{
  "compilerOptions": {
    "target": "es2017",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true,
    "useDefineForClassFields": true,
    "allowJs": false,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "declaration": true,
    "rootDir": "./src",
    "outDir": "./build",
    "baseUrl": "./"
  },
  "include": ["src"],
}

Method touch() doesn't resave the session

Renew expiries please!

Hey! Hello code! Seems like touch() is not working. No matter where I place it in code. Session puts new data in store with save() but does not renew expiries of key and cookie.

I guess touch() is the method I looking for, it suppose to be same as express-session's one

app.decorateRequest('authorize', async function (user: UserAttributes) {
    const req: FastifyRequest = this
    await req.session.touch() // not working
    req.session.data.user = user
    req.session.data.auth = true
    await req.session.save()
    await req.session.touch() // pathetic attempt
  })

I do it with decorator, cool thing.

Please Dear @mgcrea help me with renewing sessions! I don't want to switch to other package((

Upgrade to fastify v4

Hi, we're currently using this plugin to handle our sessions since it allows for custom sign/unsign functions.
Fastify 4 has been released yesterday and we cannot upgrade everything to fastify 4 since this plugin is not yet compatible with it.
I don't think there is actually anything to update code-wise, just upgrading should be fine.

[FSTDEP006] FastifyDeprecation

Using this plugin with fastify 3.9.1 results in FastifyDeprecation: You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: sessionStore

Fastify Session don't save value to redis in NestJS

When I try to save a value in the Redis Session in NestJS with the Fastify adapter, it does not give me an error, but this value is not saved in the Redis database and is visible locally within the function and not in another. Although during the Fastify session registration, the session id value, which is also saved in the cookie, is visible in the database. And I can't figure it out why it doesn't save the values ​​I want to insert in Redis

My Code:

//Register Session (main.ts)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { FastifyInstance } from 'fastify';
import fastifyCookie, { CookieSerializeOptions } from '@fastify/cookie';
import fastifyCsrf from '@fastify/csrf-protection';
import Redis from 'ioredis';
import RedisStore from '@mgcrea/fastify-session-redis-store';
import fastifySession from '@mgcrea/fastify-session';
import { randomBytes } from 'crypto';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  const fastifyInstance: FastifyInstance = app.getHttpAdapter().getInstance();
  fastifyInstance
    .addHook('onRequest', async (req, res) => {
      req.socket['encrypted'] = process.env.NODE_ENV === 'production';
      res.header('X-Powered-By', 'CyberSecurity');
    })
    .decorateReply('setHeader', function (name: string, value: unknown) {
      this.header(name, value);
    })
    .decorateReply('end', function () {
      this.send('');
    });
  const configService = app.get(ConfigService);
  const port = configService.get<string>('PORT', '');
  
  // XCSRF - Protection
  await app.register(fastifyCsrf, {
    sessionPlugin: '@fastify/cookie',
    cookieKey: 'csrf-token',
    cookie: (cookieOptions: CookieSerializeOptions) => ({
      httpOnly: true,
      sameSite: 'strict',
      path: '/',
      secure: false,
      signed: false,
      ...cookieOptions,
    }),
    secret: randomBytes(32).toString('base64'),
  } as any);

  // Session
  const redisClient = new Redis({
    host: process.env.REDIS_HOST,
    port: Number(process.env.REDIS_PORT),
  });
  await app.register(fastifySession, {
    secret: randomBytes(32).toString('base64'),
    store: new RedisStore({
      client: redisClient,
      prefix: 'session:',
      ttl: Number(process.env.REFIS_TTL),
    }),
    cookieName: 'facebook-sid',
    cookie: {
      httpOnly: true,
      sameSite: 'strict',
      path: '/',
      secure: false,
      signed: false,
      maxAge: Number(process.env.REDIS_TTL),
    },
  });

  await app.listen(port);
}
bootstrap();




// Set value & read from Session (app.controller.ts)
@Get('/csrf-token')
  async get(@Req() req, @Res() res) {
    const csrfToken = await res.generateCsrf();
    req.session.csrfToken = csrfToken;
    //req.session.set(csrfToken);
    console.log(req.session.csrfToken); //return req.session.csrfToken => string token
    res.send({ 'csrf-token': csrfToken });
  }

  @Get('/csrf-token2')
  async get2(@Req() req) {
    console.log(req.session.csrfToken);
    return { 'Session Data:': req.session.csrfToken }; //return req.session.csrfToken => undefined
  }

Is there anything that needs to be done to save session to store immediately?

If I do request.session.set('userAccountId', newUserAccountId), is there anything else I need to do to ensure that this session is immediately updated in Redis?

I am debugging the root cause of why there is a (short) delay between when we set request.session.set('userAccountId', newUserAccountId) and when this value is available to all instances running our HTTP service.

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.