Guide: Create Brand API Route
In the previous two chapters, you created a Brand Module that added the concepts of brands to your application, then created a workflow to create a brand. In this chapter, you'll expose an API route that allows admin users to create a brand using the workflow from the previous chapter.
An API Route is an endpoint that acts as an entry point for other clients to interact with your MedusaGo customizations, such as the admin dashboard, storefronts, or third-party systems.
The MedusaGo core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
1. Create the API Route#
You create an API route handler as a function in a Go file under a sub-directory of the src/api directory. The handler is a function with the signature func(req *medusahttp.MedusaRequest, res *medusahttp.MedusaResponse) error.
So, to create the handler of the API route at /admin/brands, create the file src/api/admin/brands/route.go with the following content:
// POST creates a brand with createBrandWorkflow.
func POST(req *medusahttp.MedusaRequest, res *medusahttp.MedusaResponse) error {
// the body was validated by the validation middleware
body := req.ValidatedBody.(*dal.Record)
name, _ := body.Get("name")
brand, err := workflows.CreateBrandWorkflow.Run(req.Ctx(), req.Scope,
workflows.CreateBrandWorkflowInput{Name: name.(string)})
if err != nil {
return err
}
out := dal.NewRecord()
out.Set("brand", brand)
return res.JSON(out)
}
You create a handler function whose name, POST, is the HTTP method of the API route you're exposing.
The function receives two parameters: a MedusaRequest object to access request details, and a MedusaResponse object to return or manipulate the response. The MedusaRequest object's Scope property is the container that holds Framework tools and custom and core modules' services.
In the API route's handler, you execute the CreateBrandWorkflow by calling its Run method with a context, the container req.Scope, and the workflow's input. req.Ctx() is the context of the request that you pass to the workflow. You get the brand's name from the request body's validated parameters, which you receive in the ValidatedBody property of MedusaRequest. It's a *dal.Record once the validation middleware, which you'll add next, validates the body.
You return a JSON response with the created brand using the res.JSON method. If the workflow fails, you return its error, and MedusaGo's error handler creates the response.
Then, at the end of the file, export the route file's handlers in a Route variable: var Route = medusahttp.Route{POST: POST}. Route has a property for each HTTP method. You'll add the handler of the GET method in a later chapter. The path of the route isn't in the file: you set it when you register the route in step 4.
2. Create Validation Schema#
The API route you created accepts the brand's name in the request body. So, you'll create a schema used to validate incoming request body parameters.
MedusaGo uses a validation library inspired by Zod to create validation schemas, in the zod package of the Framework. These schemas are then used to validate incoming request bodies or query parameters.
You create a validation schema in a Go file under a sub-directory of the src/api directory. So, create the file src/api/admin/brands/validators.go with the following content:
// Package brands holds the /admin/brands API routes.
package brands
import "github.com/hitkey-io/medusago/packages/core/framework/src/zod"
// PostAdminCreateBrand validates the body of POST /admin/brands: an object
// with a `name` string.
var PostAdminCreateBrand = zod.Object(
zod.F("name", zod.String()),
)
You export a validation schema that expects in the request body an object having a name property whose value is a string.
3. Add Validation Middleware#
A middleware is a function executed before the route handler when a request is sent to an API Route. It's useful to guard API routes, parse custom request body types, and apply validation on an API route.
MedusaGo provides a ValidateAndTransformBody middleware, in the http/utils package of the Framework, that accepts a validation schema and returns a response error if a request is sent with body parameters that don't satisfy the validation schema.
Create the file src/api/admin/brands/middlewares.go with the following content to add the validation middleware on the API route you created in the previous steps:
// Middlewares are the middlewares of the /admin/brands routes.
var Middlewares = []medusahttp.MiddlewareRoute{
{
Matcher: "/admin/brands",
Methods: []string{"POST"},
Middlewares: []medusahttp.MiddlewareFunction{
httputils.ValidateAndTransformBody(httputils.Schema(PostAdminCreateBrand)),
},
},
}
You define the middlewares as a list of medusahttp.MiddlewareRoute values. In the middleware route, you define three properties:
Matcher: a string or regular expression indicating the API route path to apply the middleware on. You pass the create brand's route/admin/brands.Methods: the HTTP methods to restrict the middleware to, which isPOST.Middlewares: the list of middlewares to apply on the route. You pass theValidateAndTransformBodymiddleware, passing it the schema you created earlier. The schema is wrapped byhttputils.Schema, as the middleware accepts a function that returns the schema of a request.
The MedusaGo application will now validate the body parameters of POST requests sent to /admin/brands to ensure they match the validation schema. If not, an error is returned in the response specifying the issues to fix in the request body, such as Invalid request: Field 'name' is required.
4. Register the Routes and Middlewares#
Unlike in Medusa, MedusaGo doesn't scan the src/api directory for route files and middlewares: you register them with the application, so that you control which ones it loads.
Create the file src/api/routes.go with the following content, to list the route files of your project. Each one has the path of the route and its handlers:
// Routes are the API routes of the project.
var Routes = []medusahttp.RouteFile{
{Path: "/admin/brands", Route: brands.Route},
}
Then, in src/api/middlewares.go, export the list of middlewares of your project. For now, it's the middlewares of the brands routes: var Middlewares = brands.Middlewares.
Finally, pass both lists to the CLI in main.go, with the Routes and Middlewares options:
func main() {
// define the module links and consume the workflow hooks before the
// application starts
if err := links.Define(); err != nil {
log.Fatal(err)
}
hooks.Register()
os.Exit(cli.Run(os.Args[1:], cli.Options{
Config: medusaConfig,
Routes: api.Routes,
Middlewares: api.Middlewares,
}))
}
The main function also defines the module links and consumes a workflow hook, which you'll add in the next chapters. For now, pass only the Config, Routes, and Middlewares options.
Test API Route#
To test out the API route, start the MedusaGo application with the following command:
Since the /admin/brands API route has an /admin prefix, it's only accessible by authenticated admin users.
So, to retrieve an authenticated token of your admin user, send a POST request to the /auth/user/emailpass API Route:
curl -X POST 'http://localhost:9000/auth/user/emailpass' \
-H 'Content-Type: application/json' \
--data-raw '{
"email": "admin@medusa-test.com",
"password": "supersecret"
}'
Make sure to replace the email and password with your admin user's credentials.
Then, send a POST request to /admin/brands, passing the token received from the previous request in the Authorization header:
curl -X POST 'http://localhost:9000/admin/brands' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {token}' \
--data '{
"name": "Acme"
}'
This returns the created brand in the response:
{
"brand": {
"id": "01J7AX9ES4X113HKY6C681KDZJ",
"name": "Acme",
"created_at": "2024-09-09T08:09:34.244Z",
"updated_at": "2024-09-09T08:09:34.244Z"
}
}
Troubleshooting#
Returned Empty Array or No Validation
If you sent a request to the API route and the body isn't validated, or the route returns a 404 error, make sure you've passed the Routes and Middlewares of your project to cli.Run in main.go. This is a common mistake that can lead to the route or the middleware not being applied.
Summary#
By following the previous example chapters, you implemented a custom feature that allows admin users to create a brand. You did that by:
- Creating a module that defines and manages a
brandtable in the database. - Creating a workflow that uses the module's service to create a brand record, and implements the compensation logic to delete that brand in case an error occurs.
- Creating an API route that allows admin users to create a brand.
Next Steps: Associate Brand with Product#
Now that you have brands in your MedusaGo application, you want to associate a brand with a product, which is defined in the Product Module.
In the next chapters, you'll learn how to build associations between data models defined in different modules.