2

I wanted to try to rewrite some parts of my code using fp-ts and considered to refactor the following method:

export const createApiServer = () => {
    logger.info("Starting World Server API");

    const apiServer = express()
        // 3rd party middleware
        .use(helmet())
        .use(bodyParser.json())
        .use(cors())
        // Root route
        .get("/", (_, result) => result.sendStatus(httpStatusCodes.OK))
        // 404 - Not Found
        .use((_, result) => result.sendStatus(httpStatusCodes.NOT_FOUND))
        // 500 - Internal Server Error
        .use(errorLoggerMiddleware)
        .use(errorResponseMiddleware);

    logger.verbose(`Initialized World Server API in ${process.uptime().toFixed(3)} seconds`);

    return apiServer;
};

After reading about the IO type for sideffects I tried using it as follows:

const info = (message: string) => new IO(() => log.info(message));
const verbose = (message: string) => new IO(() => log.verbose(message));

const setupApiServer = () =>
    new IO(() =>
        express()
            .use(helmet())
            .use(bodyParser.json())
            .use(cors())
            .get("/", (_, result) => result.sendStatus(httpStatusCodes.OK))
            .use((_, result) => result.sendStatus(httpStatusCodes.NOT_FOUND))
            .use(errorLoggerMiddleware)
            .use(errorResponseMiddleware)
    );

export const createApiServer = () =>
    info("Starting World Server API")
        .chain(setupApiServer)
        .chain((apiServer) =>
            verbose(`Initialized World Server API in ${process.uptime().toFixed(3)} seconds`)
                .map(() => apiServer)
        )
        .run();

It feels rather weird to arbitrarily use the map method to pipe through the apiServer object in the last chain call just to log inbetween receiving it and send it further. I'd like to know whether this is correct or whether there are any different patterns for this.

Christian Ivicevic
  • 10,071
  • 7
  • 39
  • 74

1 Answers1

5

You can use chainFirst.

With chainFirst you can refactor this

import { pipe } from "fp-ts/lib/pipeable";
import * as IO from "fp-ts/lib/IO";

declare const log: (m: string) => IO.IO<void>
declare const a: IO.IO<number>

pipe(
  a,
  IO.chain((a) => pipe(log(`Got ${a}`), IO.map(() => a)))
)

into this

pipe(
  a,
  IO.chainFirst((a) => log(`Got ${a}`))
)
Denis Frezzato
  • 957
  • 6
  • 15
  • 1
    This is the correct answer. Chain first lets you use the result of a computation and then return that same computation. It's the perfect way to log and then return the value as if you never logged at all. – user1713450 Dec 30 '20 at 02:59