Skip to content

Middleware

Express middleware is (req, res, next) => void. You call next() to pass control to the next layer, or call res.json() to short-circuit. Echo middleware follows the same pipeline idea but uses Go’s error-return pattern and closures:

// Echo middleware signature
func(next echo.HandlerFunc) echo.HandlerFunc

A middleware returns a new HandlerFunc that wraps next. Call next(c) to pass control forward, or return an error to abort.

TypeScript
// Express middleware
function requestLogger(req, res, next) {
console.log(`${req.method} ${req.path}`);
next();
}
app.use(requestLogger);
Go
// Echo middleware
func RequestLogger(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
fmt.Printf("%s %s\n", c.Request().Method, c.Request().URL.Path)
return next(c) // pass control forward
}
}
e.Use(RequestLogger)

Echo ships several production-ready middlewares in the middleware sub-package.

TypeScript
// Express with popular packages
import morgan from 'morgan';
import cors from 'cors';
app.use(morgan('combined'));
app.use(cors({ origin: '*' }));
// No built-in recover — need express-async-errors or similar
Go
// Echo built-ins — no extra packages needed
import "github.com/labstack/echo/v4/middleware"
e.Use(middleware.Logger()) // structured request log to stdout
e.Use(middleware.Recover()) // catches panics, returns 500
e.Use(middleware.CORS()) // default: allows all origins
// Restrict CORS to specific origins:
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://example.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost},
}))

Just like Express, order matters. Middleware registered first runs first. Logger and Recover should always be outermost so they see every request and catch every panic.

e.Use(middleware.Logger()) // 1st — log all requests
e.Use(middleware.Recover()) // 2nd — catch panics from any later handler
e.Use(authMiddleware) // 3rd — auth checks after logging is set up

In Express you pass middleware directly to a route. Echo supports the same with .Use on a group.

TypeScript
// Express — per-route middleware
app.get('/admin', authMiddleware, adminHandler);
// Express — router-level
const adminRouter = express.Router();
adminRouter.use(authMiddleware);
adminRouter.get('/', adminHandler);
app.use('/admin', adminRouter);
Go
// Echo — per-route middleware
e.GET("/admin", adminHandler, authMiddleware)
// Echo — group-level middleware
admin := e.Group("/admin")
admin.Use(authMiddleware)
admin.GET("", adminHandler)
admin.GET("/users", adminUsersHandler)
TypeScript
// Express JWT-like auth middleware
function requireAuth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ message: 'unauthorized' });
try {
req.user = verifyToken(token);
next();
} catch {
res.status(401).json({ message: 'invalid token' });
}
}
Go
// Echo custom auth middleware
func RequireAuth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
header := c.Request().Header.Get("Authorization")
if header == "" {
return echo.NewHTTPError(http.StatusUnauthorized, "missing token")
}
parts := strings.SplitN(header, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return echo.NewHTTPError(http.StatusUnauthorized, "bad token format")
}
claims, err := verifyToken(parts[1])
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "invalid token")
}
// Store claims in context for downstream handlers
c.Set("user", claims)
return next(c)
}
}

Run this locally — it needs the Echo module and a network port.

What is the signature of an Echo middleware function?
Which built-in middleware catches panics and returns a 500 response?
How do you apply middleware to a specific route group only?