Skip to content

Instantly share code, notes, and snippets.

@tchak
Last active May 12, 2021 18:40
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tchak/4fd02f72ba49cc932bf1d00a32dca1a1 to your computer and use it in GitHub Desktop.
Save tchak/4fd02f72ba49cc932bf1d00a32dca1a1 to your computer and use it in GitHub Desktop.
Remix withBody
import type { ActionFunction, Request } from 'remix';
import { BaseSchema, ValidationError } from 'yup';
type NextActionFunction<Body> = (body: Body) => ReturnType<ActionFunction>;
export async function withBody(
request: Request,
config: (router: ActionRouter) => void
) {
const router = new ActionRouter();
config(router);
return router.request(request);
}
async function withValidatedBody<Input, Output>(
request: Request,
schema: BaseSchema<Input, any, Output>,
next: NextActionFunction<Output>,
fallback: NextActionFunction<ValidationError>
) {
const params = new URLSearchParams(await request.text());
const body = Object.fromEntries(params);
try {
const data = await schema.validate(body);
// await here allows to throw additional validation errors from the next middleware
return await next(data);
} catch (error) {
if (ValidationError.isError(error)) {
return fallback(error as ValidationError);
}
throw error;
}
}
class ActionRouter {
#handlers: Record<string, NextActionFunction<Request>> = {};
#fallback: NextActionFunction<ValidationError> = (error) => {
throw error;
};
error(fallback: NextActionFunction<ValidationError>) {
this.#fallback = fallback;
return this;
}
post<Input, Output>(
schema: BaseSchema<Input, any, Output>,
next: NextActionFunction<Output>
) {
this.#handlers['post'] = (request: Request) =>
withValidatedBody(request, schema, next, (error) =>
this.#fallback(error)
);
return this;
}
put<Input, Output>(
schema: BaseSchema<Input, any, Output>,
next: NextActionFunction<Output>
) {
this.#handlers['put'] = (request: Request) =>
withValidatedBody(request, schema, next, (error) =>
this.#fallback(error)
);
return this;
}
patch<Input, Output>(
schema: BaseSchema<Input, any, Output>,
next: NextActionFunction<Output>
) {
this.#handlers['patch'] = (request: Request) =>
withValidatedBody(request, schema, next, (error) =>
this.#fallback(error)
);
return this;
}
delete(next: NextActionFunction<void>) {
this.#handlers['delete'] = () => next();
return this;
}
request(request: Request): ReturnType<ActionFunction> {
const callback = this.#handlers[request.method.toLowerCase()];
if (callback) {
return callback(request);
}
throw new Error(`No handler registered for "${request.method}" method`);
}
}
// In your route
const signUpSchema = Yup.object().shape({
email: Yup.string().email().required(),
password: Yup.string().min(6).required(),
});
export const action: ActionFunction = ({ request }) =>
withSession(request, (session) =>
withBody(request, (router) =>
router
.post(signUpSchema, async ({ email, password }) => {
// do something
return redirect('/');
})
.error((error) => {
session.flash('error', error);
return redirect('/signup');
})
)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment