import {
Http, // use to create http
Response, // use to respond user
Router, // use to create router
useReq, // farrow-hooks for accessing the original request of node.js http module
useRes, // farrow-hooks for accessing the original response of node.js http module
useRequestInfo, // farrow-hooks for accessing the request info
useBasenames, // farrow-hooks for accessing the basename list
usePrefix, // farrow-hooks for accessing the prefix string which is euqal basenames.join('')
} from 'farrow-http'
create a http
type HttpPipelineOptions = {
// basename list, farrow-http will cut the basename from request.pathname
basenames?: string[]
// options for parsing req body, learn more: https://github.com/cojs/co-body#options
body?: BodyOptions
// options for parsing req cookies, learn more: https://github.com/jshttp/cookie#options
cookie?: CookieOptions
// options for parsing req query, learn more: https://github.com/ljharb/qs
query?: QueryOptions
// injecting contexts per request
contexts?: (params: {
req: IncomingMessage
requestInfo: RequestInfo
basename: string
}) => ContextStorage | Promise<ContextStorage>
// enable log or not
logger?: boolean | LoggerOptions
}
// learn more about RouterPipeline below.
type HttpPipeline = RouterPipeline & {
// http.handle(req, res), handle request and respond to user, you can use this function to make farrow-http work in expressjs/koajs or other web framework in node.js
handle: (req: IncomingMessage, res: ServerResponse) => Promise<void>
// the same args of http.createServer().listen(...), create a server and listen to port
listen: (...args: Parameters<Server['listen']>) => Server
// just create a server with http.handle(req, res), don't listen to any port
server: () => Server
}
// logger options
type LoggerOptions = {
// handle logger result string
transporter?: (str: string) => void
}
Response
can be used to describe the shape of the real server response, farrow-http will perform it later
type ResponseInfo = {
status?: Status
headers?: Headers
cookies?: Cookies
body?: Body
vary?: string[]
}
type Response = {
// response info
info: ResponseInfo
// check response content type
// response.is('json') => 'json' | false
is: (...types: string[]) => string | false
// merger all responses
merge: (...responses: Response[]) => Response
// set string response body
string: (value: string) => Response
// set json response body
json: (value: JsonType) => Response
// set html response body
html: (value: string) => Response
// set text response body
text: (value: string) => Response
// redirect response
redirect: (url: string, options?: { usePrefix?: boolean }) => Response
// set stream response body
stream: (stream: Stream) => Response
// set file response body
file: (filename: string) => Response
// set vary header fields
vary: (...fileds: string[]) => Response
// set response cookie
cookie: (name: string, value: string | number | null, options?: Cookies.SetOption) => Response
// set response cookies
cookies: (cookies: { [key: string]: string | number | null }, options?: Cookies.SetOption) => Response
// set response header
header: (name: string, value: Value) => Response
// set response headers
headers: (headers: Headers) => Response
// set response status
status: (code: number, message?: string) => Response
// set buffer response body
buffer: (buffer: Buffer) => Response
// set empty content response body
empty: () => Response
// set attachment response header
attachment: (filename?: string) => Response
// set custom response body
custom: (handler?: CustomBodyHandler) => Response
// set content-type via mime-type/extname
type: (type: string) => Response
}
create a router
// learn more about Pipeline from Farrow-Pipeline API: https://github.com/Lucifier129/farrow/blob/master/docs/pipeline.md
type RouterPipeline = Pipeline<RequestInfo, MaybeAsyncResponse> & {
// capture the response body if the specific type is matched, should returning response in callback function
capture: <T extends keyof BodyMap>(type: T, callback: (body: BodyMap[T]) => MaybeAsyncResponse) => void
// add sub route and return a route-pipeline which can handle the matched request info
route: (name: string) => Pipeline<RequestInfo, MaybeAsyncResponse>
// serve static assets
serve: (name: string, dirname: string) => void
// match specific request via router-request-schema and return a schema-pipeline which can handle the matched request info
match: <T extends RouterRequestSchema>(
schema: T,
options?: MatchOptions,
) => Pipeline<TypeOfRequestSchema<T>, MaybeAsyncResponse>
}
type RouterRequestSchema = {
// match pathname of req via https://github.com/pillarjs/path-to-regexp
pathname: Pathname
// match method of req.method, default is GET, supports multiple methods as array
method?: string | string[]
// match the params parsed by path-to-regexp
params?: RouterSchemaDescriptor
// match the req query
query?: RouterSchemaDescriptor
// match the req body
body?: Schema.FieldDescriptor | Schema.FieldDescriptors
// match the req headers
headers?: RouterSchemaDescriptor
// match the rqe cookies
cookies?: RouterSchemaDescriptor
}
type MatchOptions = {
// if true, it will throw error when there are no middlewares handle the request, or it will calling next()
block?: boolean
// if given, it will be called when Router-Request-Schema was failed, if it returned Response in sync or async way, that would be the final response of middleware
onSchemaError?(error: ValidationError): Response | void | Promise<Response | void>
}
const router = Router()
// all fileds of router-request-schema list below
// learn more about Schema Builder from Farrow-Schema API: https://github.com/Lucifier129/farrow/blob/master/docs/schema.md
router
.match({
pathname: '/product/:id',
method: 'POST',
params: {
id: Number,
},
query: {
a: Number,
b: String,
c: Boolean,
},
body: {
a: Number,
b: String,
c: Boolean,
},
headers: {
a: Number,
b: String,
c: Boolean,
},
cookies: {
a: Number,
b: String,
c: Boolean,
},
})
.use(async (request) => {
console.log('request', request)
})
Since farrow v1.2.0
, a new feature router-url-schema
is supported. it combines { pathname, params, query }
into { url }
, and use Template literal types to extract the type info
// the same schema as above but in a more compact form
router
.match({
url: '/product/<id:number>?<a:number>&<b:string>&<c:boolean>',
method: 'POST',
body: {
a: Number,
b: String,
c: Boolean,
},
headers: {
a: Number,
b: String,
c: Boolean,
},
cookies: {
a: Number,
b: String,
c: Boolean,
},
})
.use(async (request) => {
console.log('request', request)
})
A dynamic parameter has the form <key:type>
.
- if it was placed in
pathname
(before?
in a url), it will regard asparams[key] = type
. the order is matter - if it was placed in
querystring
(after?
in a url), it will regard asquery[key] = type
. the order is't matter
Dynamic parameter support modifier
(learn more from here), has the form:
<key?:type>
means optional, the corresponding type is{ key?: type }
, the corresponding pattern is/:key?
<key*:type>
means zero or more, the corresponding type is{ key?: type[] }
, the corresponding pattern is/:key*
<key+:type>
means one or more, the corresponding type is{ key: type[] }
, the corresponding pattern is/:key+
A static parameter can only be placed in querystring
, it will regard as literal string type
.
For example: /?<a:int>&b=2
has the type { pathname: string, query: { a: number, b: '2' } }
The supported types in <key:type>
are list below:
string
-> tsstring
number
-> tsnumber
boolean
-> tsboolean
id
-> tsstring
, butfarrow-schema
will ensure it's not emptyint
-> tsnumber
, butfarrow-schema
will ensure it's integerfloat
-> tsnumber
{*+}
-> use the string wrapped{}
asstring literal type
. eg.{abc}
is type"abc"
, onlystring literal type
is supported
router[get|post|put|patch|head|delte|options](url, schema?, options?)
is supported as shortcut of router.match({ url, method: get|post|put|patch|head|delte|options }, options?)
router.get('/get0/<arg0:int>?<arg1:int>').use((request) => {
return Response.json({
type: 'get',
request,
})
})
http.use(() => {
// original request
let req = useReq()
})
http.use(() => {
// original response
let res = useRes()
})
http.use((request0) => {
// request1 in here is equal to request0, but we can calling useRequestInfo in any custom hooks
let request1 = useRequestInfo()
})
const http = Http({
basename: ['/base0'],
})
http.route('/base1', () => {
// basenames will be ['/base0', '/base1'] if user accessed /base0/base1
let basenames = useBasenames()
})
const http = Http({
basename: ['/base0'],
})
http.route('/base1', () => {
// prefix will be '/base0/base1' if user accessed /base0/base1
let prefix = usePrefix()
})