| name | fp-backend |
| description | Functional programming patterns for Node.js/Deno backend development using fp-ts, ReaderTaskEither, and functional dependency injection |
| category | Development & Code Tools |
| source | antigravity |
| tags | ["typescript","node","api","ai","template","prisma"] |
| url | https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/fp-backend |
fp-ts Backend Patterns
Functional programming patterns for building type-safe, testable backend services using fp-ts.
When to Use
- You are building or refactoring a Node.js or Deno backend with fp-ts.
- The task involves dependency injection, service composition, or typed backend errors with
ReaderTaskEither.
- You need functional backend architecture patterns rather than isolated utility snippets.
Core Concepts
ReaderTaskEither (RTE)
The ReaderTaskEither<R, E, A> type is the backbone of functional backend development:
- R (Reader): Dependencies/environment (database, config, logger)
- E (Either left): Error type
- A (Either right): Success value
import * as RTE from 'fp-ts/ReaderTaskEither'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/function'
type Deps = {
db: DatabaseClient
logger: Logger
config: Config
}
type AppError =
| { _tag: 'NotFound'; resource: string; id: string }
| { _tag: 'ValidationError'; message: string }
| { _tag: 'DatabaseError'; cause: unknown }
| { _tag: 'Unauthorized'; reason: string }
const getUser = (id: string): RTE.ReaderTaskEither<Deps, AppError, User> =>
pipe(
.<>(),
.(
(
.(db..(id)),
.((e): ({ : , : e })),
.(
user
? .(user)
: .({ : , : , id })
),
.( .( logger.()))
)
)
)
Service Layer Patterns
Defining Service Modules
Structure services as modules exporting RTE functions:
import * as RTE from 'fp-ts/ReaderTaskEither'
import * as TE from 'fp-ts/TaskEither'
import * as A from 'fp-ts/Array'
import { pipe } from 'fp-ts/function'
type UserDeps = {
db: DatabaseClient
hasher: PasswordHasher
mailer: EmailService
}
type UserError =
| { _tag: 'UserNotFound'; id: string }
| { _tag: 'EmailExists'; email: string }
| { _tag: 'InvalidPassword' }
export const create = (
input: CreateUserInput
): RTE.ReaderTaskEither<UserDeps, UserError, User> =>
pipe(
RTE.ask<UserDeps>(),
RTE.(
(
(input.),
.(
.(hasher.(input.))
),
.(
.(
db..({
...input,
: hashedPassword,
})
)
)
)
)
)
findById = (
:
): .<, , > =>
(
.<>(),
.(
(
.(db..({ : { id } })),
.(
user
? .(user)
: .({ : , id })
)
)
)
)
findMany = (
:
): .<, , <>> =>
(
.<>(),
.(
.(
(
.,
.(, db..({
: params.,
: params.,
})),
.(, db..()),
.( ({
: users,
total,
...params,
}))
)
)
)
)
checkEmailUnique = (
:
): .<, , > =>
(
.<>(),
.(
(
.(db..({ : { email } })),
.(
existing
? .({ : , email })
: .()
)
)
)
)
Composing Services
import * as UserService from './user.service'
import * as ProductService from './product.service'
import * as PaymentService from './payment.service'
type OrderDeps = UserService.UserDeps &
ProductService.ProductDeps &
PaymentService.PaymentDeps & {
db: DatabaseClient
}
export const createOrder = (
userId: string,
items: OrderItem[]
): RTE.ReaderTaskEither<OrderDeps, OrderError, Order> =>
pipe(
RTE.Do,
RTE.bind('user', () =>
pipe(
UserService.findById(userId),
RTE.mapLeft(toOrderError)
)
),
.(