Easily use Effect for Next.js Route Handlers, Server Actions, Pages and Forms
Next Effect is a library that makes it easy to use Effect for Next.js Route Handlers, Server Actions, Pages and Forms. It provides helpful utilities for working with Next.js using Effect.
- Write Server Actions using Effect. Learn more →
- Write progressively-enhanced Forms using Effect. Learn more →
- Write Route Handlers using Effect. Learn more →
- Full type-safety for all server-side responses. Learn more →
- OpenTelemetry tracing for each request. Learn more →
- Install the Neft Effect package
$ pnpx jsr add next-effect
- Create a Server Action handler
import { makeServerActionHandler } from "next-effect"
export class InternalServerError extends S.TaggedError<InternalServerError>()("InternalServerError", {
success: S.Boolean,
message: S.String,
reason: S.String,
}) {}
export class InvalidPayload extends S.TaggedError<InvalidPayload>()("InvalidPayload", {
success: S.Boolean,
message: S.String,
reason: S.String,
}) {}
export const invalidPayload = new InvalidPayload({ success: false, message: "Invalid payload", reason: 'invalid-payload' })
export const internalServerError = new InternalServerError({ success: false, message: "Internal server error", reason: 'internal-server-error' })
const makeServerAction = makeServerActionHandler({
errors: {
invalidPayload: ({ error, schema, payload }) => invalidPayload,
unexpected: (cause: Cause.Cause<unknown>) => internalServerError,
},
})
// lib/actions/create-team.ts
export const CreateTeamPayload = S.Struct({
name: S.String,
userId: S.String,
})
export const CreateTeamFailedError extends S.TaggedError<CreateTeamFailedError>()("CreateTeamFailedError", {
success: S.Boolean,
message: S.String,
reason: S.String,
}) {}
export const CreateTeamSuccess = S.Struct({
success: S.Literal(true),
message: S.String,
})
export const createTeam = makeServerAction(CreateTeamPayload, async (payload: S.Schema.Type<typeof CreateTeamPayload>) => Effect.gen(function* () {
const random = yield* Random.next
if (random > 0.5) {
return yield* Effect.fail(new CreateTeamFailedError({ success: false, message: "Failed to create team", reason: 'create-team-failed' }))
}
return CreateTeamSuccess.make({
success: true,
message: "Team created successfully.",
})
}))
// components/create-team.tsx
export const CreateTeamForm = () => {
async function createTeam() {
const result: CreateTeamSuccess | CreateTeamFailedError | InvalidPayload | InternalServerError = await createTeam({ name: 'John Doe', userId: '123' })
// 👇 We can easily handle errors, since all our errors are fully typed
if (result._tag === "CreateTeamFailedError") {
sonner.error("Oops! Failed to create team: " + result.message)
}
// Continue…
}
}
- Create a Route Handler
export class InternalServerError extends S.TaggedError<InternalServerError>()("InternalServerError", {
success: S.Boolean,
message: S.String,
reason: S.String,
}) {}
export class InvalidPayload extends S.TaggedError<InvalidPayload>()("InvalidPayload", {
success: S.Boolean,
message: S.String,
reason: S.String,
}) {}
export const invalidPayload = new InvalidPayload({ success: false, message: "Invalid payload", reason: 'invalid-payload' })
export const internalServerError = new InternalServerError({ success: false, message: "Internal server error", reason: 'internal-server-error' })
const createRouteHandler = makeRouteHandler({
errors: {
invalidPayload: ({ error, schema, payload }) => invalidPayload,
unexpected: (cause: Cause.Cause<unknown>) => internalServerError
},
// 👇 Define your response types here (these are used for extracting response status codes, encoding etc.)
responses: [Unauthorized, TestSuccess, TextResponse, BytesResponse]
})
export const GET = createRouteHandler(Effect.sync(() => {
const name = yield* Next.ensureRequestSchema(S.Struct({ name: S.String }))
return { success: true, message: `API key created for ${name.name}.` }
}))
- Create a Form Handler
// todo: write this
TODO Write Docs
TODO Write docs
- Clone this repository
- Install dependencies using
deno install
- Run the tests using
deno test
Made with ❤️ in Helsinki, Finland.
Published under MIT License.
- OTEL setup
- Work with Effect Dev Tools
- Request tracing
- Easily add context to traces
- Way to define response types / errors
- Schemas to define response types (raw, text), status codes
- Headers