An open API service indexing awesome lists of open source software.

https://github.com/andywer/srv

📡 Functional node server. Composable routing. Take a request, return a response.
https://github.com/andywer/srv

Last synced: 8 months ago
JSON representation

📡 Functional node server. Composable routing. Take a request, return a response.

Awesome Lists containing this project

README

          

SRV


Node.js servers rethought: Functional, lean, performant.


Travis build status
Travis build status


What if we were to write [express](https://github.com/expressjs/express) from scratch in 2019…?

Would we use async functions and promises? Would we make it more functional? With TypeScript in mind?

Sure we would! So here we go.

🚀  **Built for ES2017+ & TypeScript**

🔌  **Functional - Take a request, return a response**

☝️  **Explicit - Clear static types**

🗜  **Almost no dependencies & less than 1000 lines of code**



⚠️ Status: Experimental ⚠️

## At a glance

```ts
import {
Response,
Route,
Router,
Service
} from "@andywer/srv"

const greet = Route.GET("/welcome", async (request) => {
const name = request.query.name

return Response.JSON({
name: "Greeting service",
welcome: name ? `Hello, ${name}!` : `Hi there!`
})
})

const service = Service(Router([ greet ]))

service.listen(8080)
.catch(console.error)
```

## Documentation

Find some documentation and sample code here. Work in progress right now.

* [Routing](./docs/routing.md)
* [Middleware](./docs/middleware.md)

## Features

Async functions

No callbacks. Leverage modern day features instead for an optimal developer experience.

```ts
import { Response, Route } from "@andywer/srv"

const greet = Route.GET("/health", async () => {
try {
const stats = await fetchHealthMetrics()
return Response.JSON({
operational: true,
stats
})
} catch (error) {
return Response.JSON(500, {
operational: false
})
}
})
```

Functional route handlers

Take a request, return a response. Lean, clean, easy to test and debug.

```ts
import { Response, Route, Router } from "@andywer/srv"
import { queryUserByID } from "./database/users"

const getUser = Route.GET("/user/:id", async request => {
const userID = request.params.id
const user = await queryUserByID(userID)

if (!user) {
return Response.JSON(404, {
message: `User ${userID} not found`
})
}

const headers = {
"Last-Modified": user.updated_at || user.created_at
}
return Response.JSON(200, headers, user)
})

export const router = Router([
getUser
])
```

No mutations

Stop passing data from middlewares to route handlers by dumping it in an untypeable `context`. Take the request object, extend it, pass it down to the route handler.

By applying middlewares in a direct and explicit manner, the passed requests and responses are completely type-safe, even if customized by middlewares.

```ts
import { Middleware, Request, RequestHandler, Service } from "@andywer/srv"
import { Logger } from "./logger"

export default function LoggingMiddleware(logger: Logger): Middleware {
return async (request: Request, next: RequestHandler) => {
const requestWithLogger = request.derive({
log: logger
})
// typeof requestWithLogger.log === Logger
return next(requestWithLogger)
}
}
```

```ts
import { composeMiddlewares, Service } from "@andywer/srv"
import logger from "./logger"
import router from "./routes"

const applyMiddlewares = composeMiddlewares(
LoggingMiddleware(logger),
SomeOtherMiddleware()
)

const service = Service(applyMiddlewares(router))
```

Everything is a function

The code base is relatively simple. Middlewares, routes and routers, they are all just implementations of the following function type:

```ts
type RequestHandler = (request: Request, next?: NextCallback) => Response | Promise
```

```ts
type NextCallback = (req: Request) => Response | Promise
```

## Debugging

Set the `DEBUG` environment variable to `srv:*` to get some debug logging:

```
$ DEBUG=srv:* node ./dist/my-server
```

## License

MIT