Socket
Socket
Sign inDemoInstall

bigriver

Package Overview
Dependencies
135
Maintainers
1
Versions
45
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

bigriver

A fast well-typed back-end prototyping framework


Version published
Maintainers
1
Weekly downloads
2
increased by100%

Weekly downloads

Readme

Source

Bigriver

Bigriver is a highly-opinionated web framework on the top of Node.js, Redis and MongoDB. Unlike other JavaScripts frameworks, Bigriver is natively written on Typescript and applied to the modern patterns to ensure the type-safety of the framework.

Install

Bigriver requires Node.js 14+, Redis and MongoDB. It's highly recommended to run Bigriver on Unix-like operating system with Nginx.

Just simply run

yarn add bigriver

or

npm install bigriver

to install the framework.

Initialization

Bigriver uses a series of init functions like React's hook functions for its services.

Initialize app (Fastify server)

Since Bigriver is built on the top of Fastify web framework, we can simply use "useApp" function to init the server.

const app = useApp({
  routers: [
    /* Your routers */
  ],
  trustProxy: true,
})

const PORT = 8080
const ADDRESS = '127.0.0.1' // 0.0.0.0 if use docker

app.listen(PORT, ADDRESS, () => console.log(`Running on port ${PORT}`))

Initialize MongoDB Connection

Simply use a mongodb uri to connect to it. Since Bigriver follows the best pattern we considered for our scenes, we don't provide any more options for MongoDB connection to avoid deprecations and performance decays.

await useMongoDB('mongodb://[URI]')

Initialize Redis Connection

Redis connection options are the same as IORedis' connection options.

await useRedis({
  host: '',
  password: '',
  port: 6379,
  keyPrefix: '',
  enableAutoPipelining: true,
  reconnectOnError: () => 2,
})

Router

Define a router in Bigriver is very easy. Let's look at an example of an user registration API to have a first impression on it.

const router = useRouter({
  prefix: '/auth',
  middlewares: [
    /* Router-level middlewares for all routes */
  ],
})

router.post('/signup', {
  schema: {
    body: Type.Object({
      countryCode: Type.Integer({ minimum: 1, maximum: 999 }),
      mobile: Type.String({
        pattern: StringPattern.Integer,
        minLength: 6,
        maxLength: 13,
      }),
      password: Type.String({ minLength: 6, maxLength: 32 }),
    }),
  },
  handler: async req => {
    const { countryCode, mobile, password } = req.body

    // ...

    return { success: true }
  },
})

For the schema and handler part, we use the totally same logic as Fastify's. However, we introduced TypeBox to our framework to define the schema (validation). With TypeBox, we can easily define typed schemas and then we can use those type inference later in our request and response interfaces in the handler function.

Beside TypeBox, we also provide StringPattern, StringType and StringTo three utility classes to help you define and convert string-related types. We also provide a LiteralType class to help you deal with the condition of multiple literals (based on Json Schema's Union Type).

Let's take a look on an example of our code

const router = useRouter({
  prefix: '/activities',
  middlewares: [AuthGuard, RateLimit({ timeWindow: 60000, maxRequests: 20 })],
})

// Get activities
router.get('/', {
  alternativePaths: ['/users/:userId/activities'],
  middlewares: [
    /* Route only middlewares */
  ],
  schema: {
    params: Type.Object({
      userId: Type.Optional(StringType.ObjectID),
    }),
    querystring: Type.Object({
      direction: LiteralType.anyOf(['from', 'to']),
      type: Type.Optional(LiteralType.anyOf(['reaction', 'postComment'])),
      limit: Type.Optional(StringType.Integer),
      before: Type.Optional(StringType.DateTime),
      after: Type.Optional(StringType.DateTime),
    }),
  },
  handler: async req => {
    const { user } = req.state
    const { direction, type } = req.query

    const limit = StringTo.Number(req.query.limit)
    const before = StringTo.Date(req.query.before)
    const after = StringTo.Date(req.query.after)

    if (!req.params.userId && !user.permissions.includes('manageEntity')) {
      throw new Forbidden()
    }

    return []
  },
})

In this part of code, you can also see that there is an alternativePath option. It's used to create a alternative path for a route (without the prefix). It's helpful when you want a handler to handle two slightly different routes without repeating your codes.

You can also find that we use middlewares here. When you set middlewares in useRouter, these middlewares will be applied to all routes under the router. When you set middlewares for a route itself, it will only be applied for itself.

It's highly recommended to also install http-errors (a Node.js module) to throw user-friendly HTTP errors, such as 403 Forbidden in this part of code.

Middleware

We use the "AuthGuard" as an example to show you how to write a middleware. Writing a middleware is just like the way you write your handlers for routers.

export const AuthGuard: Middleware = async req => {
  if (!req.headers.authorization) {
    throw new Unauthorized()
  }

  const token = req.headers.authorization.substr(7)
  const result = await AuthService.verifyToken(token)

  req.state.user = result
}

By default, we don't have a key called "user" under our request state. Therefore, it's important to create a type file for typescript to recoginize it. You may simply create a type.d.ts in your source code directory.

declare module 'fastify' {
  interface RequestState {
    user: {
      _id: string
      permissions: Permission[]
    }
  }
}

By modify the request state, you will be allowed to get them in your route handler like this:

router.get('/', {
  schema: {},
  handler: async req => {
    const { user } = req.state
    // ...
  },
})

MongoDB & Data Model

In typical Web frameworks, there is usually a concept called ORM (Object-relational Mapping) or ODM (Object-document Mapping). However, the implementation of ORMs or ODMs usually brings performance decays and makes everything complicated. In Bigriver, we don't use any relational mapping. Instead, we use TypeScript's language features to implement a Virtual Model. Let's quickly look at an example:

export interface Place extends BaseModel, Timestamps {
  name: string
  description: string
  location: GeoJsonPoint
  owner: Ref<User>
}

export const PlaceModel = useModel<Place>({
  collection: 'place',
  timestamps: true,
  sync: true,
  indexes: [
    [{ owner: 1 }],
    [{ 'location': '2dsphere' }],
  ],
})

const asyncfunc = async () => {
  await PlaceModel.create({
    name: 'New Place',
    description: 'Something',
    location: {
      type: 'Point',
      coordinates: [0, 0],
    },
    owner: /* An user model's ObjectID */,
  })

  const places = await PlaceModel.find({}).toArray()

  await PlaceModel.populate(places, [
    {
      model: UserModel,
      path: 'owner',
      select: ['nickname', 'avatarUrl'],
    },
  ])

  console.log(places)
}

asyncfunc()

Basically, the query functions are just the same syntax as Node.js MongoDB driver (Because we built Bigriver on the top of it). Our useModel function will create a virtual model for you. The virtual model will automatically create the indexes and handle timestamps for your model. In this case, PlaceModel.populate will help you to join references based on BSON ObjectIDs. The usage of this populate function is similar to the one in Mongoose.

How to feedback

If you have any question about Bigriver, you can just open an issue, create a pull request, or send an email to opensource@topos.company for further discussions.

FAQs

Last updated on 09 Jun 2021

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc