xialvjun / farrow

A type friendly web framework for node.js written by TypeScript

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

𝓕π“ͺ𝓻𝓻𝓸𝔀

Farrow Description

npm version Build Status Documentation Maintenance License: MIT Twitter: guyingjie129

Farrow is a functional-style web framework for node.js written by TypeScript

Documentation | Example

Benefits

  • Expressive HTTP middleware like Koa but no need to modify req/res or ctx
  • Strongly typed and type-safe from request to response via powerful schema-based validation
  • Provide React-Hooks-like mechanism which is useful for reusing code and integrating other parts of Server like database connection
  • Easy to learn and use if you were experienced in expressjs/koajs

farrow

Environment Requirement

  • TypeScript 4.1
  • Node.js 14.0.0

Usage

How to install

# via npm
npm install --save farrow farrow-pipeline farrow-schema farrow-http

# via yarn
yarn add farrow farrow-pipeline farrow-schema farrow-http

How to setup a development environment

add scripts to your package.json

{
  "scripts": {
    "dev": "farrow dev",
    "build": "farrow build",
    "start": "farrow start"
  }
}

and then:

  • npm run dev for developing
  • npm run build for bundling the source code
  • npm run start for runing the output code of bundler

farrow assumes that your source code is in src folder, and the output code is in dist folder.

You can use farrow.config.js to change the default configuration, see the documentation for more detail.

How to setup a server

import { Http, Response } from 'farrow-http'

const http = Http()

// add http middleware
http.use(() => {
  // returning response in middleware
  return Response.text(`Hello Farrow`)
})

http.listen(3000)

How to serve static assets

http.serve('/static', dirname)

How to respond text or json or html or file

// respond text
http.use(() => {
  return Response.text(`Farrow`)
})

// respond json
http.use(() => {
  return Response.json({
    farrow: true,
    data: {},
  })
})

// respond html
http.use(() => {
  return Response.html(`<h1>Farrow</h1>`)
})

// respond file
http.use(() => {
  return Response.file(filename)
})

How to access request info

http.use((request) => {
  // access request pathname
  console.log('pathname', request.pathname)

  // access request method
  console.log('method', request.method)

  // access request query
  console.log('query', request.query)

  // access request body
  console.log('body', request.body)

  // access request headers
  console.log('headers', request.headers)

  // access request cookies
  console.log('cookies', request.cookies)
})

How to match specific request

Click Router-Url-Schema to read more

// http.match(schema).use(...middlewares)
// farrow will validate request info and extract the data for middlewares
// schema has the similar shape like request info: { pathname, method, query, body, headers, cookies, params }
// the params is readed from path-to-regexp if you config schema.pathname to be /product/:id, and params is equal to { id }
// learn more about pathname: https://github.com/pillarjs/path-to-regexp#usage
http
  .match({
    pathname: '/product',
    // if method was not given, the default value wounld be `GET`.
    query: {
      productId: Number,
    },
  })
  .use((request) => {
    // productId is a number
    console.log('productId', request.query.productId)
  })

// or using routing-methods
http.get('/get0/<arg0:int>?<arg1:int>').use((request) => {
  return Response.json({
    type: 'get',
    request,
  })
})

How to pass new request info for downstream middleware

http.use((request, next) => {
  // no need to modify the request, just calling next(new_request) with a new request info
  return next({
    ...request,
    pathname: '/fixed',
  })
})

http.use((request) => {
  // request pathname will be '/fixed'
  console.log('pathname', request.pathname)
})

How to filter and manipulate response in upstream middleware

http.use(async (request, next) => {
  // next() returning response received from downstream
  let response = await next()
  let headers = {
    'header-key': 'header-value',
  }
  // filter or merge response and return
  return Response.headers(headers).merge(response)
})

http.use(async (request) => {
  return Response.json(request)
})

How to set response headers

http.use(() => {
  return Response.header('a', '1').header('b', '2').text('ok')
})

// or

http.use(() => {
  return Response.headers({
    a: '1',
    b: '2',
  }).text('ok')
})

How to set response cookies

http.use(() => {
  return Response.cookie('a', '1').cookie('b', '2').text('ok')
})

// or

http.use(() => {
  return Response.cookies({
    a: '1',
    b: '2',
  }).text('ok')
})

How to set response status

http.use(() => {
  return Response.status(404, 'Not Found').html('some text')
})

How to redirect

http.use(() => {
  return Response.redirect(targetUrl)
})

How to merge responses

let response0 = Response.status(200)
let response1 = Response.header('a', '1')
let response2 = Response.header('b', '2')
let response3 = Response.cookie('c', '3')

let response = Response.merge(response0, response1, response2, response3)
// or
let response = response0.merge(response1, response2, response3)

How to add router

Router() has the same methods like Http() except http.listen(...) and http.server()

import { Http, Router, Response } from 'farrow-http'

// create http
const http = Http()

// create product router
const product = Router()

// create user router
const user = Router()

// add sub route for product
http.route('/product').use(product)

// add sub route for user
http.route('/user').use(user)

http.listen(3000)

// handle product router
// this will match /product/:id
product.get('/<id:int>').use(async (request) => {
  return Response.json({
    productId: request.params.id,
  })
})

// this will match /product/info
product.get('/info').use(async (request) => {
  return Response.json({
    productInfo: {},
  })
})

// handle user router
// this will match /user/:id
user.get('/<id:int>').use(async (request) => {
  return Response.json({
    userId: request.params.id,
  })
})

// this will match /user/info
user.get('/info').use(async (request) => {
  return Response.json({
    userInfo: {},
  })
})

How to add view-engine

Farrow provide an official server-side rendering library based on React, but you can implement your own via Response.html(...) or Response.stream(...).

# via npm
npm install --save react react-dom farrow-react

# via yarn
yarn add react react-dom farrow-react
import React from 'react'
import { useReactView } from 'farrow-react'
// use Link to auto prefix basename came from http.route(name, ...) or router.route(name, ...)
import { Link } from 'farrow-react/Link'

http.use(() => {
  let ReactView = useReactView({
    docType: '<!doctype html>', // optional, specify the doctype in html response
    useStream: true, // optional, if ture it will use ReactDOMServer.renderToNodeStream internally
  })

  return ReactView.render(
    <>
      <h1>Hello Farrow-React</h1>
      <Link href="/">Home</Link>
    </>,
  )
})

How to write a farrow hooks

Click here to learn more.

import { createContext } from 'farrow-pipeline'
import { Http, HttpMiddleware } from 'farrow-http'
import { useReactView } from 'farrow-react'

// declare an interface
interface User {
  id: string
  name: string
  email: string
}

// define a farrow context via interface
const UserContext = createContext<User | null>(null)

// define a provider middleware
const UserProvider = (): HttpMiddleware => {
  return async (request, next) => {
    // assume defining somewhere
    let session = SessionContext.get()
    let db = DbContext.get()

    if (!request?.cookies?.token) {
      return next()
    }

    let userId = await session.read(request?.cookies?.token)

    let user = await db.query({
      User: {
        token,
      },
    })

    UserContext.set(user)

    return next()
  }
}

const http = Http()

http.use(UserProvider())

http
  .match({
    pathname: '/userinfo',
  })
  .use(async (request, next) => {
    let ReactView = useReactView()
    // assert context value is not null or undefined and return context value
    let user = UserContext.assert()

    return ReactView.render(<div>{JSON.stringify(user)}</div>)
  })

Development

# git clone and run command below to initilize project
npm run init

# test
npm run test

Author

πŸ‘€ Jade Gu

🀝 Contributing

Contributions, issues and feature requests are welcome!

Feel free to check issues page.

Show your support

Give a ⭐️ if this project helped you!

πŸ“ License

Copyright Β© 2021 Jade Gu.

This project is MIT licensed.

About

A type friendly web framework for node.js written by TypeScript

License:MIT License


Languages

Language:TypeScript 92.0%Language:JavaScript 3.9%Language:CSS 2.9%Language:HTML 1.2%