Repository: beatlabs/patron
Pull request #437 (2021-11-11 12:30:51)

HTTP v2 Component
<!--
Thanks for taking precious time for making a PR.

Before creating a pull request, please make sure:
- Your PR solves one problem for which a issue exist and a solution has been discussed
- You have read the guide for contributing
  - See https://github.com/beatlabs/patron/blob/master/README.md#how-to-contribute
- You signed all your commits (otherwise we won't be able to merge the PR)
  - See https://github.com/beatlabs/patron/blob/master/SIGNYOURWORK.md
- You added unit tests for the new functionality
- You mention in the PR description which issue it is addressing, e.g. "Resolves #123"
-->

## Which problem is this PR solving?

See #436 

## Short description of the changes

<!-- REQUIRED -->


gmaz42: Should we also change some `.md` file to document all the new goodies?
mantzas: @gmaz42 @teivah @peterklijn @tpaschalis This should be all the discussed changes. Sorry for adding some general fixes in this PR, I could move them another if it makes your work easier. Thanks
teivah: @mantzas You're getting some conflicts.

Patch:
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 9a230618d..db35f4bcc 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -3,7 +3,7 @@ Welcome to the patron framework project.
 
 - Please search for existing issues to avoid creating duplicate bugs/feature requests.
 - Please be respectful and considerate of others when commenting on issues.
-- Please provide as much information as possible so we all understand the issue.
+- Please provide as much information as possible, so we all understand the issue.
 
 -->
 
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index ea2bc63e7..b19a7cf6a 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -2,7 +2,7 @@
 Thanks for taking precious time for making a PR.
 
 Before creating a pull request, please make sure:
-- Your PR solves one problem for which a issue exist and a solution has been discussed
+- Your PR solves one problem for which an issue exist and a solution has been discussed
 - You have read the guide for contributing
   - See https://github.com/beatlabs/patron/blob/master/README.md#how-to-contribute
 - You signed all your commits (otherwise we won't be able to merge the PR)
diff --git a/Makefile b/Makefile
index 871c2d52e..afc90549c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
- DOCKER = docker
+DOCKER = docker
 
 default: test
 
diff --git a/README.md b/README.md
index 2c1720e0f..2bb3fb31d 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,8 @@ The entry point of the framework is the `Service`. The `Service` uses `Component
     - [Kafka](docs/components/async/Kafka.md)
     - [AMQP](docs/components/async/AMQP.md)
     - [AWS SQS (deprecated)](docs/components/async/AWSSQS.md)
-  - [HTTP](docs/components/HTTP.md)
+  - [HTTP (deprecated)](docs/components/HTTP.md)
+  - [HTTP v2](docs/components/HTTPv2.md)
   - [gRPC](docs/components/gRPC.md)
   - [AWS SQS](docs/components/SQS.md)
   - [AMQP](docs/components/AMQP.md)
diff --git a/cache/redis/redis.go b/cache/redis/redis.go
index cdb888c00..419bb377c 100644
--- a/cache/redis/redis.go
+++ b/cache/redis/redis.go
@@ -16,7 +16,7 @@ type Cache struct {
 	ctx context.Context
 }
 
-// Options exposes the options struct from go-redis package.
+// Options exposes the struct from go-redis package.
 type Options redis.Options
 
 // New returns a new Redis client that will be used as the cache store.
diff --git a/client/http/http.go b/client/http/http.go
index eeb23cbfa..6e7f81fe2 100644
--- a/client/http/http.go
+++ b/client/http/http.go
@@ -41,12 +41,12 @@ func init() {
 	prometheus.MustRegister(reqDurationMetrics)
 }
 
-// Client interface of a HTTP client.
+// Client interface of an HTTP client.
 type Client interface {
 	Do(req *http.Request) (*http.Response, error)
 }
 
-// TracedClient defines a HTTP client with tracing integrated.
+// TracedClient defines an HTTP client with tracing integrated.
 type TracedClient struct {
 	cl *http.Client
 	cb *circuitbreaker.CircuitBreaker
diff --git a/client/sns/message.go b/client/sns/message.go
index de96598b5..b70c13b88 100644
--- a/client/sns/message.go
+++ b/client/sns/message.go
@@ -22,13 +22,13 @@ const (
 	tracingTargetUnknown     = "unknown"
 )
 
-// MessageBuilder helps building messages to be sent to SNS.
+// MessageBuilder helps to build messages to be sent to SNS.
 type MessageBuilder struct {
 	err   error
 	input *sns.PublishInput
 }
 
-// NewMessageBuilder creates a new MessageBuilder that helps creating messages.
+// NewMessageBuilder creates a new MessageBuilder that helps to create messages.
 //
 // Deprecated: The SNS client package is superseded by the `github.com/beatlabs/client/sns/v2` package.
 // Please refer to the documents and the examples for the usage.
@@ -110,7 +110,7 @@ func (b *MessageBuilder) WithStringAttribute(name string, value string) *Message
 	return b
 }
 
-// WithStringArrayAttribute attaches an array of strings attribute to the message.
+// WithStringArrayAttribute attaches an array of strings attributes to the message.
 //
 // The accepted values types are string, number, boolean and nil. Any other type will throw an error.
 func (b *MessageBuilder) WithStringArrayAttribute(name string, values []interface{}) *MessageBuilder {
@@ -163,7 +163,7 @@ func (b *MessageBuilder) WithBinaryAttribute(name string, value []byte) *Message
 	return b
 }
 
-// addAttributeValue creates a base attribute value and adds it to the the list of attribute values.
+// addAttributeValue creates a base attribute value and adds it to the list of attribute values.
 func (b *MessageBuilder) addAttributeValue(name string, dataType attributeDataType) *sns.MessageAttributeValue {
 	attributeValue := &sns.MessageAttributeValue{}
 	attributeValue.SetDataType(string(dataType))
diff --git a/client/sqs/message.go b/client/sqs/message.go
index 3b3ef95be..447c3f335 100644
--- a/client/sqs/message.go
+++ b/client/sqs/message.go
@@ -10,13 +10,13 @@ import (
 
 const attributeDataTypeString string = "String"
 
-// MessageBuilder helps building messages to be sent to SQS.
+// MessageBuilder helps to build messages to be sent to SQS.
 type MessageBuilder struct {
 	err   error
 	input *sqs.SendMessageInput
 }
 
-// NewMessageBuilder creates a new MessageBuilder that helps creating messages.
+// NewMessageBuilder creates a new MessageBuilder that helps to create messages.
 //
 // Deprecated: The SQS client package is superseded by the `github.com/beatlabs/client/sqs/v2` package.
 // Please refer to the documents and the examples for the usage.
diff --git a/component/async/kafka/option.go b/component/async/kafka/option.go
index 85feb791e..59f48e8b3 100644
--- a/component/async/kafka/option.go
+++ b/component/async/kafka/option.go
@@ -13,7 +13,7 @@ import (
 // OptionFunc definition for configuring the consumer in a functional way.
 type OptionFunc func(*ConsumerConfig) error
 
-// Version option for setting the Kafka version.
+// Version for setting the Kafka version.
 func Version(version string) OptionFunc {
 	return func(c *ConsumerConfig) error {
 		if version == "" {
@@ -29,7 +29,7 @@ func Version(version string) OptionFunc {
 	}
 }
 
-// Buffer option for adjusting the incoming messages buffer.
+// Buffer for adjusting the incoming messages buffer.
 func Buffer(buf int) OptionFunc {
 	return func(c *ConsumerConfig) error {
 		if buf < 0 {
@@ -40,7 +40,7 @@ func Buffer(buf int) OptionFunc {
 	}
 }
 
-// Timeout option for adjusting the timeout of the connection.
+// Timeout for adjusting the timeout of the connection.
 func Timeout(timeout time.Duration) OptionFunc {
 	return func(c *ConsumerConfig) error {
 		c.SaramaConfig.Net.DialTimeout = timeout
@@ -48,7 +48,7 @@ func Timeout(timeout time.Duration) OptionFunc {
 	}
 }
 
-// Start option for adjusting the the starting offset.
+// Start for adjusting the starting offset.
 func Start(offset int64) OptionFunc {
 	return func(c *ConsumerConfig) error {
 		c.SaramaConfig.Consumer.Offsets.Initial = offset
@@ -56,7 +56,7 @@ func Start(offset int64) OptionFunc {
 	}
 }
 
-// StartFromOldest option for adjusting the starting offset to oldest.
+// StartFromOldest for adjusting the starting offset to oldest.
 func StartFromOldest() OptionFunc {
 	return func(c *ConsumerConfig) error {
 		c.SaramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest
@@ -64,7 +64,7 @@ func StartFromOldest() OptionFunc {
 	}
 }
 
-// StartFromNewest option for adjusting the starting offset to newest.
+// StartFromNewest for adjusting the starting offset to newest.
 func StartFromNewest() OptionFunc {
 	return func(c *ConsumerConfig) error {
 		c.SaramaConfig.Consumer.Offsets.Initial = sarama.OffsetNewest
@@ -72,7 +72,7 @@ func StartFromNewest() OptionFunc {
 	}
 }
 
-// Decoder option for injecting a specific decoder implementation.
+// Decoder for injecting a specific decoder implementation.
 func Decoder(dec encoding.DecodeRawFunc) OptionFunc {
 	return func(c *ConsumerConfig) error {
 		if dec == nil {
@@ -83,7 +83,7 @@ func Decoder(dec encoding.DecodeRawFunc) OptionFunc {
 	}
 }
 
-// DecoderJSON option for injecting json decoder.
+// DecoderJSON for injecting json decoder.
 func DecoderJSON() OptionFunc {
 	return func(c *ConsumerConfig) error {
 		c.DecoderFunc = json.DecodeRaw
diff --git a/component/async/sqs/option.go b/component/async/sqs/option.go
index acc0d74dc..e346a3423 100644
--- a/component/async/sqs/option.go
+++ b/component/async/sqs/option.go
@@ -40,9 +40,9 @@ func PollWaitSeconds(pollWaitSeconds int64) OptionFunc {
 }
 
 // VisibilityTimeout sets the time a message is invisible after it has been requested.
-// This is a built in resiliency mechanism so that, should the consumer fail to acknowledge the message within such timeout,
+// This is a built-in resiliency mechanism so that, should the consumer fail to acknowledge the message within such timeout,
 // it will become visible again and thus available for retries.
-// Allowed values are between 0 and and 12 hours in seconds.
+// Allowed values are between 0 and 12 hours in seconds.
 // If this option is unused, it defaults to the queue's default visibility settings.
 func VisibilityTimeout(visibilityTimeout int64) OptionFunc {
 	return func(f *Factory) error {
diff --git a/component/http/alive.go b/component/http/alive.go
index e7f4b6517..76ee0f56a 100644
--- a/component/http/alive.go
+++ b/component/http/alive.go
@@ -5,6 +5,9 @@ import (
 )
 
 // AliveStatus type representing the liveness of the service via HTTP component.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type AliveStatus int
 
 const (
@@ -12,9 +15,15 @@ const (
 	Alive AliveStatus = 1
 	// Unresponsive represents a state defining a Unresponsive state.
 	Unresponsive AliveStatus = 2
+
+	// AlivePath of the service.
+	AlivePath = "/alive"
 )
 
 // AliveCheckFunc defines a function type for implementing a liveness check.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type AliveCheckFunc func() AliveStatus
 
 func aliveCheckRoute(acf AliveCheckFunc) *RouteBuilder {
@@ -28,5 +37,5 @@ func aliveCheckRoute(acf AliveCheckFunc) *RouteBuilder {
 			w.WriteHeader(http.StatusOK)
 		}
 	}
-	return NewRawRouteBuilder("/alive", f).MethodGet()
+	return NewRawRouteBuilder(AlivePath, f).MethodGet()
 }
diff --git a/component/http/component.go b/component/http/component.go
index 6060bb792..a3a42e5ab 100644
--- a/component/http/component.go
+++ b/component/http/component.go
@@ -8,6 +8,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/beatlabs/patron/component/http/middleware"
 	patronErrors "github.com/beatlabs/patron/errors"
 	"github.com/beatlabs/patron/log"
 	"github.com/julienschmidt/httprouter"
@@ -24,12 +25,21 @@ const (
 
 var (
 	// DefaultAliveCheck return always live.
+	//
+	// Deprecated: Please use the new v2 package.
+	// This package is frozen and no new functionality will be added.
 	DefaultAliveCheck = func() AliveStatus { return Alive }
 	// DefaultReadyCheck return always ready.
+	//
+	// Deprecated: Please use the new v2 package.
+	// This package is frozen and no new functionality will be added.
 	DefaultReadyCheck = func() ReadyStatus { return Ready }
 )
 
 // Component implementation of HTTP.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type Component struct {
 	ac                  AliveCheckFunc
 	rc                  ReadyCheckFunc
@@ -41,7 +51,7 @@ type Component struct {
 	shutdownGracePeriod time.Duration
 	sync.Mutex
 	routes      []Route
-	middlewares []MiddlewareFunc
+	middlewares []middleware.Func
 	certFile    string
 	keyFile     string
 }
@@ -81,7 +91,7 @@ func (c *Component) createHTTPServer() *http.Server {
 	router := httprouter.New()
 	for _, route := range c.routes {
 		if len(route.middlewares) > 0 {
-			h := MiddlewareChain(route.handler, route.middlewares...)
+			h := middleware.Chain(route.handler, route.middlewares...)
 			router.Handler(route.method, route.path, h)
 		} else {
 			router.HandlerFunc(route.method, route.path, route.handler)
@@ -90,9 +100,9 @@ func (c *Component) createHTTPServer() *http.Server {
 		log.Debugf("added route %s %s", route.method, route.path)
 	}
 	// Add first the recovery middleware to ensure that no panic occur.
-	routerAfterMiddleware := MiddlewareChain(router, NewRecoveryMiddleware())
-	c.middlewares = append(c.middlewares, NewCompressionMiddleware(c.deflateLevel, c.uncompressedPaths...))
-	routerAfterMiddleware = MiddlewareChain(routerAfterMiddleware, c.middlewares...)
+	routerAfterMiddleware := middleware.Chain(router, middleware.NewRecovery())
+	c.middlewares = append(c.middlewares, middleware.NewCompression(c.deflateLevel, c.uncompressedPaths...))
+	routerAfterMiddleware = middleware.Chain(routerAfterMiddleware, c.middlewares...)
 
 	return &http.Server{
 		Addr:         fmt.Sprintf(":%d", c.httpPort),
@@ -105,6 +115,9 @@ func (c *Component) createHTTPServer() *http.Server {
 
 // Builder gathers all required and optional properties, in order
 // to construct an HTTP component.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type Builder struct {
 	ac                  AliveCheckFunc
 	rc                  ReadyCheckFunc
@@ -115,7 +128,7 @@ type Builder struct {
 	uncompressedPaths   []string
 	shutdownGracePeriod time.Duration
 	routesBuilder       *RoutesBuilder
-	middlewares         []MiddlewareFunc
+	middlewares         []middleware.Func
 	certFile            string
 	keyFile             string
 	errors              []error
@@ -124,6 +137,9 @@ type Builder struct {
 // NewBuilder initiates the HTTP component builder chain.
 // The builder instantiates the component using default values for
 // HTTP Port, Alive/Ready check functions and Read/Write timeouts.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewBuilder() *Builder {
 	var errs []error
 	return &Builder{
@@ -133,7 +149,7 @@ func NewBuilder() *Builder {
 		httpReadTimeout:     httpReadTimeout,
 		httpWriteTimeout:    httpWriteTimeout,
 		deflateLevel:        deflateLevel,
-		uncompressedPaths:   []string{"/metrics", "/alive", "/ready"},
+		uncompressedPaths:   []string{MetricsPath, AlivePath, ReadyPath},
 		shutdownGracePeriod: shutdownGracePeriod,
 		routesBuilder:       NewRoutesBuilder(),
 		errors:              errs,
@@ -165,7 +181,7 @@ func (cb *Builder) WithRoutesBuilder(rb *RoutesBuilder) *Builder {
 }
 
 // WithMiddlewares adds middlewares to the HTTP component.
-func (cb *Builder) WithMiddlewares(mm ...MiddlewareFunc) *Builder {
+func (cb *Builder) WithMiddlewares(mm ...middleware.Func) *Builder {
 	if len(mm) == 0 {
 		cb.errors = append(cb.errors, errors.New("empty list of middlewares provided"))
 	} else {
diff --git a/component/http/errors.go b/component/http/errors.go
index 4b92fb7cd..9eb557b7b 100644
--- a/component/http/errors.go
+++ b/component/http/errors.go
@@ -6,6 +6,9 @@ import (
 )
 
 // Error defines an abstract struct that can represent several types of HTTP errors.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type Error struct {
 	code    int
 	payload interface{}
@@ -27,61 +30,97 @@ func (e *Error) WithHeaders(headers map[string]string) *Error {
 }
 
 // NewValidationError creates a new validation error with default payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewValidationError() *Error {
 	return &Error{code: http.StatusBadRequest, payload: http.StatusText(http.StatusBadRequest)}
 }
 
 // NewValidationErrorWithPayload creates a new validation error with the specified payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewValidationErrorWithPayload(payload interface{}) *Error {
 	return &Error{code: http.StatusBadRequest, payload: payload}
 }
 
 // NewUnauthorizedError creates a new validation error with default payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewUnauthorizedError() *Error {
 	return &Error{code: http.StatusUnauthorized, payload: http.StatusText(http.StatusUnauthorized)}
 }
 
 // NewUnauthorizedErrorWithPayload creates a new unauthorized error with the specified payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewUnauthorizedErrorWithPayload(payload interface{}) *Error {
 	return &Error{code: http.StatusUnauthorized, payload: payload}
 }
 
 // NewForbiddenError creates a new forbidden error with default payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewForbiddenError() *Error {
 	return &Error{code: http.StatusForbidden, payload: http.StatusText(http.StatusForbidden)}
 }
 
 // NewForbiddenErrorWithPayload creates a new forbidden error with the specified payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewForbiddenErrorWithPayload(payload interface{}) *Error {
 	return &Error{code: http.StatusForbidden, payload: payload}
 }
 
 // NewNotFoundError creates a new not found error with default payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewNotFoundError() *Error {
 	return &Error{code: http.StatusNotFound, payload: http.StatusText(http.StatusNotFound)}
 }
 
 // NewNotFoundErrorWithPayload creates a new not found error with the specified payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewNotFoundErrorWithPayload(payload interface{}) *Error {
 	return &Error{code: http.StatusNotFound, payload: payload}
 }
 
 // NewServiceUnavailableError creates a new service unavailable error with default payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewServiceUnavailableError() *Error {
 	return &Error{code: http.StatusServiceUnavailable, payload: http.StatusText(http.StatusServiceUnavailable)}
 }
 
 // NewServiceUnavailableErrorWithPayload creates a new service unavailable error with the specified payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewServiceUnavailableErrorWithPayload(payload interface{}) *Error {
 	return &Error{code: http.StatusServiceUnavailable, payload: payload}
 }
 
 // NewError creates a new error with default Internal Server Error payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewError() *Error {
 	return &Error{code: http.StatusInternalServerError, payload: http.StatusText(http.StatusInternalServerError)}
 }
 
 // NewErrorWithCodeAndPayload creates a fully customizable error with the specified status code and payload.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewErrorWithCodeAndPayload(code int, payload interface{}) *Error {
 	return &Error{code: code, payload: payload}
 }
diff --git a/component/http/handler.go b/component/http/handler.go
index 58c853982..57fc2d75b 100644
--- a/component/http/handler.go
+++ b/component/http/handler.go
@@ -30,7 +30,7 @@ func handler(hnd ProcessorFunc) http.HandlerFunc {
 		// TODO : for cached responses this becomes inconsistent, to be fixed in #160
 		// the corID will be passed to all consecutive responses
 		// if it was missing from the initial request
-		corID := getOrSetCorrelationID(r.Header)
+		corID := correlation.GetOrSetHeaderID(r.Header)
 		ctx := correlation.ContextWithID(r.Context(), corID)
 		logger := log.Sub(map[string]interface{}{correlation.ID: corID})
 		ctx = log.WithContext(ctx, logger)
diff --git a/component/http/http.go b/component/http/http.go
index 7ced90b45..ae985b875 100644
--- a/component/http/http.go
+++ b/component/http/http.go
@@ -1,4 +1,4 @@
-// Package http provides a HTTP component with included observability.
+// Package http provides an HTTP component with included observability.
 package http
 
 import (
@@ -9,10 +9,16 @@ import (
 	"github.com/beatlabs/patron/encoding"
 )
 
-// Header is the http header representation as a map of strings.
+// Header is the http header representation as a map of strings
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type Header map[string]string
 
 // Request definition of the sync request model.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type Request struct {
 	Fields  map[string]string
 	Raw     io.Reader
@@ -21,6 +27,9 @@ type Request struct {
 }
 
 // NewRequest creates a new request.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewRequest(f map[string]string, r io.Reader, h map[string]string, d encoding.DecodeFunc) *Request {
 	return &Request{Fields: f, Raw: r, Headers: h, decode: d}
 }
@@ -31,6 +40,9 @@ func (r *Request) Decode(v interface{}) error {
 }
 
 // Response definition of the sync Response model.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type Response struct {
 	Payload interface{}
 	Header  Header
@@ -42,6 +54,9 @@ func NewResponse(p interface{}) *Response {
 }
 
 // ProcessorFunc definition of a function type for processing sync requests.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type ProcessorFunc func(context.Context, *Request) (*Response, error)
 
 func propagateHeaders(header Header, wHeader http.Header) {
diff --git a/component/http/metric.go b/component/http/metric.go
index bdf8f78d6..39565480c 100644
--- a/component/http/metric.go
+++ b/component/http/metric.go
@@ -5,6 +5,9 @@ import (
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
+// MetricsPath definition.
+const MetricsPath = "/metrics"
+
 func metricRoute() *RouteBuilder {
-	return NewRawRouteBuilder("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}).ServeHTTP).MethodGet()
+	return NewRawRouteBuilder(MetricsPath, promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}).ServeHTTP).MethodGet()
 }
diff --git a/component/http/logging.go b/component/http/middleware/logging.go
similarity index 90%
rename from component/http/logging.go
rename to component/http/middleware/logging.go
index 605e47e48..25f272206 100644
--- a/component/http/logging.go
+++ b/component/http/middleware/logging.go
@@ -1,4 +1,4 @@
-package http
+package middleware
 
 import (
 	"errors"
@@ -14,10 +14,29 @@ const (
 	excluded                     // '(' or ')'
 )
 
-func newStatusCodeLoggerHandler(cfg string) (statusCodeLoggerHandler, error) {
+type StatusCodeLoggerHandler struct {
+	codes []statusCode
+}
+
+func (s StatusCodeLoggerHandler) shouldLog(statusCode int) bool {
+	for _, code := range s.codes {
+		if code.isRange() {
+			if code.rangeCodes.isIncluded(statusCode) {
+				return true
+			}
+		} else {
+			if statusCode == code.exactCode {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func NewStatusCodeLoggerHandler(cfg string) (StatusCodeLoggerHandler, error) {
 	cfg = strings.TrimSpace(cfg)
 	if len(cfg) == 0 {
-		return statusCodeLoggerHandler{}, nil
+		return StatusCodeLoggerHandler{}, nil
 	}
 
 	splits := strings.Split(cfg, ";")
@@ -34,7 +53,7 @@ func newStatusCodeLoggerHandler(cfg string) (statusCodeLoggerHandler, error) {
 		} else {
 			codeRange, err := parseRange(split)
 			if err != nil {
-				return statusCodeLoggerHandler{}, fmt.Errorf("failed to parse status code range %q: %w", split, err)
+				return StatusCodeLoggerHandler{}, fmt.Errorf("failed to parse status code range %q: %w", split, err)
 			}
 
 			codes[idx] = statusCode{
@@ -42,7 +61,7 @@ func newStatusCodeLoggerHandler(cfg string) (statusCodeLoggerHandler, error) {
 			}
 		}
 	}
-	return statusCodeLoggerHandler{
+	return StatusCodeLoggerHandler{
 		codes: codes,
 	}, nil
 }
@@ -136,22 +155,3 @@ func (s *statusCodeRange) isIncluded(statusCode int) bool {
 	}
 	return statusCode > s.start && statusCode < s.end
 }
-
-type statusCodeLoggerHandler struct {
-	codes []statusCode
-}
-
-func (s statusCodeLoggerHandler) shouldLog(statusCode int) bool {
-	for _, code := range s.codes {
-		if code.isRange() {
-			if code.rangeCodes.isIncluded(statusCode) {
-				return true
-			}
-		} else {
-			if statusCode == code.exactCode {
-				return true
-			}
-		}
-	}
-	return false
-}
diff --git a/component/http/middleware.go b/component/http/middleware/middleware.go
similarity index 86%
rename from component/http/middleware.go
rename to component/http/middleware/middleware.go
index 730013bb6..3a764d054 100644
--- a/component/http/middleware.go
+++ b/component/http/middleware/middleware.go
@@ -1,4 +1,4 @@
-package http
+package middleware
 
 import (
 	"bytes"
@@ -17,19 +17,17 @@ import (
 	"sync"
 	"time"
 
-	"golang.org/x/time/rate"
-
 	"github.com/beatlabs/patron/component/http/auth"
 	"github.com/beatlabs/patron/component/http/cache"
 	"github.com/beatlabs/patron/correlation"
 	"github.com/beatlabs/patron/encoding"
 	"github.com/beatlabs/patron/log"
 	"github.com/beatlabs/patron/trace"
-	"github.com/google/uuid"
 	"github.com/opentracing/opentracing-go"
 	"github.com/opentracing/opentracing-go/ext"
 	tracinglog "github.com/opentracing/opentracing-go/log"
 	"github.com/prometheus/client_golang/prometheus"
+	"golang.org/x/time/rate"
 )
 
 const (
@@ -42,6 +40,12 @@ const (
 	anythingHeader = "*"
 )
 
+var (
+	httpStatusTracingInit          sync.Once
+	httpStatusTracingHandledMetric *prometheus.CounterVec
+	httpStatusTracingLatencyMetric *prometheus.HistogramVec
+)
+
 type responseWriter struct {
 	status              int
 	statusHeaderWritten bool
@@ -50,12 +54,6 @@ type responseWriter struct {
 	writer              http.ResponseWriter
 }
 
-var (
-	httpStatusTracingInit          sync.Once
-	httpStatusTracingHandledMetric *prometheus.CounterVec
-	httpStatusTracingLatencyMetric *prometheus.HistogramVec
-)
-
 func newResponseWriter(w http.ResponseWriter, capturePayload bool) *responseWriter {
 	return &responseWriter{status: -1, statusHeaderWritten: false, writer: w, capturePayload: capturePayload}
 }
@@ -96,11 +94,11 @@ func (w *responseWriter) WriteHeader(code int) {
 	w.statusHeaderWritten = true
 }
 
-// MiddlewareFunc type declaration of middleware func.
-type MiddlewareFunc func(next http.Handler) http.Handler
+// Func type declaration of middleware func.
+type Func func(next http.Handler) http.Handler
 
-// NewRecoveryMiddleware creates a MiddlewareFunc that ensures recovery and no panic.
-func NewRecoveryMiddleware() MiddlewareFunc {
+// NewRecovery creates a Func that ensures recovery and no panic.
+func NewRecovery() Func {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			defer func() {
@@ -124,8 +122,8 @@ func NewRecoveryMiddleware() MiddlewareFunc {
 	}
 }
 
-// NewAuthMiddleware creates a MiddlewareFunc that implements authentication using an Authenticator.
-func NewAuthMiddleware(auth auth.Authenticator) MiddlewareFunc {
+// NewAuth creates a Func that implements authentication using an Authenticator.
+func NewAuth(auth auth.Authenticator) Func {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			authenticated, err := auth.Authenticate(r)
@@ -143,12 +141,12 @@ func NewAuthMiddleware(auth auth.Authenticator) MiddlewareFunc {
 	}
 }
 
-// NewLoggingTracingMiddleware creates a MiddlewareFunc that continues a tracing span and finishes it.
+// NewLoggingTracing creates a Func that continues a tracing span and finishes it.
 // It uses Jaeger and OpenTracing and will also log the HTTP request on debug level if configured so.
-func NewLoggingTracingMiddleware(path string, statusCodeLogger statusCodeLoggerHandler) MiddlewareFunc {
+func NewLoggingTracing(path string, statusCodeLogger StatusCodeLoggerHandler) Func {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			corID := getOrSetCorrelationID(r.Header)
+			corID := correlation.GetOrSetHeaderID(r.Header)
 			sp, r := span(path, corID, r)
 			lw := newResponseWriter(w, true)
 			next.ServeHTTP(lw, r)
@@ -161,6 +159,19 @@ func NewLoggingTracingMiddleware(path string, statusCodeLogger statusCodeLoggerH
 	}
 }
 
+// NewInjectObservability injects a correlation ID unless one is already present.
+func NewInjectObservability() Func {
+	return func(next http.Handler) http.Handler {
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			corID := correlation.GetOrSetHeaderID(r.Header)
+			ctx := correlation.ContextWithID(r.Context(), corID)
+			logger := log.Sub(map[string]interface{}{correlation.ID: corID})
+			ctx = log.WithContext(ctx, logger)
+			next.ServeHTTP(w, r.WithContext(ctx))
+		})
+	}
+}
+
 func initHTTPServerMetrics() {
 	httpStatusTracingHandledMetric = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
@@ -183,10 +194,10 @@ func initHTTPServerMetrics() {
 	prometheus.MustRegister(httpStatusTracingLatencyMetric)
 }
 
-// NewRequestObserverMiddleware creates a MiddlewareFunc that captures status code and duration metrics about the responses returned;
+// NewRequestObserver creates a Func that captures status code and duration metrics about the responses returned;
 // metrics are exposed via Prometheus.
 // This middleware is enabled by default.
-func NewRequestObserverMiddleware(method, path string) MiddlewareFunc {
+func NewRequestObserver(method, path string) Func {
 	// register Prometheus metrics on first use
 	httpStatusTracingInit.Do(initHTTPServerMetrics)
 
@@ -212,8 +223,8 @@ func NewRequestObserverMiddleware(method, path string) MiddlewareFunc {
 	}
 }
 
-// NewRateLimitingMiddleware creates a MiddlewareFunc that adds a rate limit to a route.
-func NewRateLimitingMiddleware(limiter *rate.Limiter) MiddlewareFunc {
+// NewRateLimiting creates a Func that adds a rate limit to a route.
+func NewRateLimiting(limiter *rate.Limiter) Func {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			if !limiter.Allow() {
@@ -310,10 +321,10 @@ func selectByWeight(weighted map[float64]string) (string, error) {
 	return weighted[keys[len(keys)-1]], nil
 }
 
-// NewCompressionMiddleware initializes a compression middleware.
+// NewCompression initializes a compression middleware.
 // As per Section 3.5 of the HTTP/1.1 RFC, GZIP and Deflate compression methods are supported.
 // https://tools.ietf.org/html/rfc2616#section-14.3
-func NewCompressionMiddleware(deflateLevel int, ignoreRoutes ...string) MiddlewareFunc {
+func NewCompression(deflateLevel int, ignoreRoutes ...string) Func {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			if ignore(ignoreRoutes, r.URL.String()) {
@@ -355,7 +366,7 @@ func isErrConnectionReset(err error) bool {
 	errMsg := err.Error()
 
 	// See the explanation here: https://github.com/aws/aws-sdk-go/issues/2525#issuecomment-519263830
-	// It is a little bit vague, but it seems they mean that this specific error happens when we stopped reading for some reason
+	// It is a little vague, but it seems they mean that this specific error happens when we stopped reading for some reason
 	// even though there was something to read. This might have happened due to a wrong length header for example.
 	// So it might've been our error, not an error of remote server when it closes connection unexpectedly.
 	if strings.Contains(errMsg, "read: connection reset") {
@@ -448,10 +459,10 @@ func (w *dynamicCompressionResponseWriter) Close() error {
 	return nil
 }
 
-// NewCachingMiddleware creates a cache layer as a middleware
+// NewCaching creates a cache layer as a middleware
 // when used as part of a middleware chain any middleware later in the chain,
 // will not be executed, but the headers it appends will be part of the cache.
-func NewCachingMiddleware(rc *cache.RouteCache) MiddlewareFunc {
+func NewCaching(rc *cache.RouteCache) Func {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			if r.Method != http.MethodGet {
@@ -467,8 +478,8 @@ func NewCachingMiddleware(rc *cache.RouteCache) MiddlewareFunc {
 	}
 }
 
-// MiddlewareChain chains middlewares to a handler func.
-func MiddlewareChain(f http.Handler, mm ...MiddlewareFunc) http.Handler {
+// Chain chains middlewares to a handler func.
+func Chain(f http.Handler, mm ...Func) http.Handler {
 	for i := len(mm) - 1; i >= 0; i-- {
 		f = mm[i](f)
 	}
@@ -486,38 +497,14 @@ func logRequestResponse(corID string, w *responseWriter, r *http.Request) {
 	}
 
 	info := map[string]interface{}{
-		"request": map[string]interface{}{
-			"remote-address": remoteAddr,
-			"method":         r.Method,
-			"url":            r.URL,
-			"proto":          r.Proto,
-			"status":         w.Status(),
-			"referer":        r.Referer(),
-			"user-agent":     r.UserAgent(),
-			correlation.ID:   corID,
-		},
-	}
-	log.Sub(info).Debug()
-}
-
-func getOrSetCorrelationID(h http.Header) string {
-	cor, ok := h[correlation.HeaderID]
-	if !ok {
-		corID := uuid.New().String()
-		h.Set(correlation.HeaderID, corID)
-		return corID
-	}
-	if len(cor) == 0 {
-		corID := uuid.New().String()
-		h.Set(correlation.HeaderID, corID)
-		return corID
-	}
-	if cor[0] == "" {
-		corID := uuid.New().String()
-		h.Set(correlation.HeaderID, corID)
-		return corID
-	}
-	return cor[0]
+		correlation.ID:   corID,
+		"method":         r.Method,
+		"url":            r.URL,
+		"status":         w.Status(),
+		"remote-address": remoteAddr,
+		"proto":          r.Proto,
+	}
+	log.FromContext(r.Context()).Sub(info).Debug("request log")
 }
 
 func span(path, corID string, r *http.Request) (opentracing.Span, *http.Request) {
diff --git a/component/http/ready.go b/component/http/ready.go
index 443b676c6..3de667291 100644
--- a/component/http/ready.go
+++ b/component/http/ready.go
@@ -5,6 +5,9 @@ import (
 )
 
 // ReadyStatus type.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type ReadyStatus int
 
 const (
@@ -12,9 +15,15 @@ const (
 	Ready ReadyStatus = 1
 	// NotReady represents a state defining a NotReady state.
 	NotReady ReadyStatus = 2
+
+	// ReadyPath of the service.
+	ReadyPath = "/ready"
 )
 
 // ReadyCheckFunc defines a function type for implementing a readiness check.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type ReadyCheckFunc func() ReadyStatus
 
 func readyCheckRoute(rcf ReadyCheckFunc) *RouteBuilder {
@@ -28,5 +37,5 @@ func readyCheckRoute(rcf ReadyCheckFunc) *RouteBuilder {
 			w.WriteHeader(http.StatusOK)
 		}
 	}
-	return NewRawRouteBuilder("/ready", f).MethodGet()
+	return NewRawRouteBuilder(ReadyPath, f).MethodGet()
 }
diff --git a/component/http/route.go b/component/http/route.go
index 6474fcba0..6cf8bc4f8 100644
--- a/component/http/route.go
+++ b/component/http/route.go
@@ -10,16 +10,17 @@ import (
 	"github.com/beatlabs/patron/cache"
 	"github.com/beatlabs/patron/component/http/auth"
 	httpcache "github.com/beatlabs/patron/component/http/cache"
+	"github.com/beatlabs/patron/component/http/middleware"
 	errs "github.com/beatlabs/patron/errors"
 	"golang.org/x/time/rate"
 )
 
-// Route definition of a HTTP route.
+// Route definition of an HTTP route.
 type Route struct {
 	path        string
 	method      string
 	handler     http.HandlerFunc
-	middlewares []MiddlewareFunc
+	middlewares []middleware.Func
 }
 
 // Path returns route path value.
@@ -33,7 +34,7 @@ func (r Route) Method() string {
 }
 
 // Middlewares returns route middlewares.
-func (r Route) Middlewares() []MiddlewareFunc {
+func (r Route) Middlewares() []middleware.Func {
 	return r.middlewares
 }
 
@@ -43,12 +44,15 @@ func (r Route) Handler() http.HandlerFunc {
 }
 
 // RouteBuilder for building a route.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type RouteBuilder struct {
 	method        string
 	path          string
 	jaegerTrace   bool
 	rateLimiter   *rate.Limiter
-	middlewares   []MiddlewareFunc
+	middlewares   []middleware.Func
 	authenticator auth.Authenticator
 	handler       http.HandlerFunc
 	routeCache    *httpcache.RouteCache
@@ -69,7 +73,7 @@ func (rb *RouteBuilder) WithRateLimiting(limit float64, burst int) *RouteBuilder
 }
 
 // WithMiddlewares adds middlewares.
-func (rb *RouteBuilder) WithMiddlewares(mm ...MiddlewareFunc) *RouteBuilder {
+func (rb *RouteBuilder) WithMiddlewares(mm ...middleware.Func) *RouteBuilder {
 	if len(mm) == 0 {
 		rb.errors = append(rb.errors, errors.New("middlewares are empty"))
 	}
@@ -152,7 +156,7 @@ func (rb *RouteBuilder) MethodTrace() *RouteBuilder {
 func (rb *RouteBuilder) Build() (Route, error) {
 	// parse a list of HTTP numeric status codes that must be logged
 	cfg, _ := os.LookupEnv("PATRON_HTTP_STATUS_ERROR_LOGGING")
-	statusCodeLogger, err := newStatusCodeLoggerHandler(cfg)
+	statusCodeLogger, err := middleware.NewStatusCodeLoggerHandler(cfg)
 	if err != nil {
 		return Route{}, fmt.Errorf("failed to parse status codes %q: %w", cfg, err)
 	}
@@ -165,21 +169,21 @@ func (rb *RouteBuilder) Build() (Route, error) {
 		return Route{}, errors.New("method is missing")
 	}
 
-	var middlewares []MiddlewareFunc
+	var middlewares []middleware.Func
 	if rb.jaegerTrace {
 		// uses Jaeger/OpenTracing and Patron's response logging
-		middlewares = append(middlewares, NewLoggingTracingMiddleware(rb.path, statusCodeLogger))
+		middlewares = append(middlewares, middleware.NewLoggingTracing(rb.path, statusCodeLogger))
 	}
 
 	// uses a custom Patron metric for HTTP responses (with complete status code)
 	// it does not use Jaeger/OpenTracing
-	middlewares = append(middlewares, NewRequestObserverMiddleware(rb.method, rb.path))
+	middlewares = append(middlewares, middleware.NewRequestObserver(rb.method, rb.path))
 
 	if rb.rateLimiter != nil {
-		middlewares = append(middlewares, NewRateLimitingMiddleware(rb.rateLimiter))
+		middlewares = append(middlewares, middleware.NewRateLimiting(rb.rateLimiter))
 	}
 	if rb.authenticator != nil {
-		middlewares = append(middlewares, NewAuthMiddleware(rb.authenticator))
+		middlewares = append(middlewares, middleware.NewAuth(rb.authenticator))
 	}
 	if len(rb.middlewares) > 0 {
 		middlewares = append(middlewares, rb.middlewares...)
@@ -189,7 +193,7 @@ func (rb *RouteBuilder) Build() (Route, error) {
 		if rb.method != http.MethodGet {
 			return Route{}, errors.New("cannot apply cache to a route with any method other than GET ")
 		}
-		middlewares = append(middlewares, NewCachingMiddleware(rb.routeCache))
+		middlewares = append(middlewares, middleware.NewCaching(rb.routeCache))
 	}
 
 	return Route{
@@ -201,6 +205,9 @@ func (rb *RouteBuilder) Build() (Route, error) {
 }
 
 // NewFileServer constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewFileServer(path string, assetsDir string, fallbackPath string) *RouteBuilder {
 	var ee []error
 
@@ -257,6 +264,9 @@ func NewFileServer(path string, assetsDir string, fallbackPath string) *RouteBui
 }
 
 // NewRawRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewRawRouteBuilder(path string, handler http.HandlerFunc) *RouteBuilder {
 	var ee []error
 
@@ -272,6 +282,9 @@ func NewRawRouteBuilder(path string, handler http.HandlerFunc) *RouteBuilder {
 }
 
 // NewRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	var ee []error
 
@@ -286,52 +299,82 @@ func NewRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return &RouteBuilder{path: path, errors: ee, handler: handler(processor)}
 }
 
-// NewGetRouteBuilder constructor.
+// NewGetRouteBuilder constructor
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewGetRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodGet()
 }
 
 // NewHeadRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewHeadRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodHead()
 }
 
 // NewPostRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewPostRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodPost()
 }
 
 // NewPutRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewPutRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodPut()
 }
 
 // NewPatchRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewPatchRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodPatch()
 }
 
 // NewDeleteRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewDeleteRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodDelete()
 }
 
 // NewConnectRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewConnectRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodConnect()
 }
 
 // NewOptionsRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewOptionsRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodOptions()
 }
 
 // NewTraceRouteBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewTraceRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
 	return NewRouteBuilder(path, processor).MethodTrace()
 }
 
 // RoutesBuilder creates a list of routes.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 type RoutesBuilder struct {
 	routes []Route
 	errors []error
@@ -370,6 +413,9 @@ func (rb *RoutesBuilder) Build() ([]Route, error) {
 }
 
 // NewRoutesBuilder constructor.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func NewRoutesBuilder() *RoutesBuilder {
 	return &RoutesBuilder{}
 }
diff --git a/component/http/v2/check.go b/component/http/v2/check.go
new file mode 100644
index 000000000..d2c283687
--- /dev/null
+++ b/component/http/v2/check.go
@@ -0,0 +1,72 @@
+package v2
+
+import (
+	"net/http"
+
+	"github.com/beatlabs/patron/log"
+)
+
+// AliveStatus type representing the liveness of the service via HTTP component.
+type AliveStatus int
+
+// ReadyStatus type.
+type ReadyStatus int
+
+const (
+	// Alive represents a state defining an Alive state.
+	Alive AliveStatus = 1
+	// Unhealthy represents an unhealthy alive state.
+	Unhealthy AliveStatus = 2
+
+	// Ready represents a state defining a Ready state.
+	Ready ReadyStatus = 1
+	// NotReady represents a state defining a NotReady state.
+	NotReady ReadyStatus = 2
+
+	// AlivePath of the component.
+	AlivePath = "/alive"
+	// ReadyPath of the component.
+	ReadyPath = "/ready"
+)
+
+// ReadyCheckFunc defines a function type for implementing a readiness check.
+type ReadyCheckFunc func() ReadyStatus
+
+// LivenessCheckFunc defines a function type for implementing a liveness check.
+type LivenessCheckFunc func() AliveStatus
+
+// LivenessCheckRoute returns a route for liveness checks.
+func LivenessCheckRoute(acf LivenessCheckFunc) (*Route, error) {
+	f := func(w http.ResponseWriter, r *http.Request) {
+		val := acf()
+		switch val {
+		case Alive:
+			w.WriteHeader(http.StatusOK)
+		case Unhealthy:
+			w.WriteHeader(http.StatusServiceUnavailable)
+		default:
+			w.WriteHeader(http.StatusServiceUnavailable)
+			log.FromContext(r.Context()).Errorf("wrong live check status returned: %d", val)
+		}
+	}
+
+	return NewRoute(http.MethodGet, AlivePath, f)
+}
+
+// ReadyCheckRoute returns a route for ready checks.
+func ReadyCheckRoute(rcf ReadyCheckFunc) (*Route, error) {
+	f := func(w http.ResponseWriter, r *http.Request) {
+		val := rcf()
+		switch val {
+		case Ready:
+			w.WriteHeader(http.StatusOK)
+		case NotReady:
+			w.WriteHeader(http.StatusServiceUnavailable)
+		default:
+			w.WriteHeader(http.StatusServiceUnavailable)
+			log.FromContext(r.Context()).Errorf("wrong ready check status returned: %d", val)
+		}
+	}
+
+	return NewRoute(http.MethodGet, ReadyPath, f)
+}
diff --git a/component/http/v2/component.go b/component/http/v2/component.go
new file mode 100644
index 000000000..fea322f4c
--- /dev/null
+++ b/component/http/v2/component.go
@@ -0,0 +1,100 @@
+// Package v2 provides a ready to use HTTP component.
+package v2
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"sync"
+	"time"
+
+	"github.com/beatlabs/patron/log"
+)
+
+const (
+	port                = 50000
+	readTimeout         = 30 * time.Second
+	writeTimeout        = 60 * time.Second
+	idleTimeout         = 240 * time.Second
+	handlerTimeout      = 59 * time.Second // should be smaller than write timeout
+	shutdownGracePeriod = 5 * time.Second
+)
+
+// Component implementation of an HTTP router.
+type Component struct {
+	port                int
+	readTimeout         time.Duration
+	writeTimeout        time.Duration
+	shutdownGracePeriod time.Duration
+	handlerTimeout      time.Duration
+	handler             http.Handler
+	mu                  sync.Mutex
+	certFile            string
+	keyFile             string
+}
+
+// New creates an HTTP component configurable by functional options.
+func New(handler http.Handler, oo ...OptionFunc) (*Component, error) {
+	if handler == nil {
+		return nil, errors.New("handler is nil")
+	}
+
+	cmp := &Component{
+		port:                port,
+		readTimeout:         readTimeout,
+		writeTimeout:        writeTimeout,
+		shutdownGracePeriod: shutdownGracePeriod,
+		handlerTimeout:      handlerTimeout,
+		handler:             handler,
+	}
+
+	for _, option := range oo {
+		err := option(cmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return cmp, nil
+}
+
+// Run starts the HTTP server and returns only if listening and/or serving failed, or if the context was canceled.
+func (c *Component) Run(ctx context.Context) error {
+	c.mu.Lock()
+	chFail := make(chan error)
+	srv := c.createHTTPServer()
+	go c.listenAndServe(srv, chFail)
+	c.mu.Unlock()
+
+	select {
+	case <-ctx.Done():
+		log.Info("shutting down HTTP component")
+		ctx, cancel := context.WithTimeout(context.Background(), c.shutdownGracePeriod)
+		defer cancel()
+		return srv.Shutdown(ctx)
+	case err := <-chFail:
+		return err
+	}
+}
+
+func (c *Component) createHTTPServer() *http.Server {
+	return &http.Server{
+		Addr:         fmt.Sprintf(":%d", c.port),
+		ReadTimeout:  c.readTimeout,
+		WriteTimeout: c.writeTimeout,
+		IdleTimeout:  idleTimeout,
+		Handler:      http.TimeoutHandler(c.handler, c.handlerTimeout, ""),
+	}
+}
+
+func (c *Component) listenAndServe(srv *http.Server, ch chan<- error) {
+	if c.certFile != "" && c.keyFile != "" {
+		log.Debugf("HTTPS component listening on port %d", c.port)
+		ch <- srv.ListenAndServeTLS(c.certFile, c.keyFile)
+		return
+	}
+
+	log.Debugf("HTTP component listening on port %d", c.port)
+	ch <- srv.ListenAndServe()
+}
diff --git a/component/http/v2/component_option.go b/component/http/v2/component_option.go
new file mode 100644
index 000000000..5f58529cd
--- /dev/null
+++ b/component/http/v2/component_option.go
@@ -0,0 +1,77 @@
+package v2
+
+import (
+	"errors"
+	"time"
+)
+
+// OptionFunc definition for configuring the component in a functional way.
+type OptionFunc func(*Component) error
+
+// TLS functional option.
+func TLS(cert, key string) OptionFunc {
+	return func(cmp *Component) error {
+		if cert == "" || key == "" {
+			return errors.New("cert file or key file was empty")
+		}
+
+		cmp.certFile = cert
+		cmp.keyFile = key
+		return nil
+	}
+}
+
+// ReadTimeout functional option.
+func ReadTimeout(rt time.Duration) OptionFunc {
+	return func(cmp *Component) error {
+		if rt <= 0*time.Second {
+			return errors.New("negative or zero read timeout provided")
+		}
+		cmp.readTimeout = rt
+		return nil
+	}
+}
+
+// WriteTimeout functional option.
+func WriteTimeout(wt time.Duration) OptionFunc {
+	return func(cmp *Component) error {
+		if wt <= 0*time.Second {
+			return errors.New("negative or zero write timeout provided")
+		}
+		cmp.writeTimeout = wt
+		return nil
+	}
+}
+
+// HandlerTimeout functional option.
+func HandlerTimeout(wt time.Duration) OptionFunc {
+	return func(cmp *Component) error {
+		if wt <= 0*time.Second {
+			return errors.New("negative or zero handler timeout provided")
+		}
+		cmp.handlerTimeout = wt
+		return nil
+	}
+}
+
+// ShutdownGracePeriod functional option.
+func ShutdownGracePeriod(gp time.Duration) OptionFunc {
+	return func(cmp *Component) error {
+		if gp <= 0*time.Second {
+			return errors.New("negative or zero shutdown grace period timeout provided")
+		}
+		cmp.shutdownGracePeriod = gp
+		return nil
+	}
+}
+
+// Port functional option.
+func Port(port int) OptionFunc {
+	return func(cmp *Component) error {
+		if port <= 0 || port > 65535 {
+			return errors.New("invalid HTTP Port provided")
+		}
+		cmp.port = port
+		return nil
+	}
+}
diff --git a/component/http/v2/observability.go b/component/http/v2/observability.go
new file mode 100644
index 000000000..84aa665d1
--- /dev/null
+++ b/component/http/v2/observability.go
@@ -0,0 +1,65 @@
+package v2
+
+import (
+	"expvar"
+	"fmt"
+	"net/http"
+	"net/http/pprof"
+
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+const (
+	// MetricsPath of the component.
+	MetricsPath = "/metrics"
+)
+
+// MetricRoute creation.
+func MetricRoute() *Route {
+	return &Route{
+		method:  http.MethodGet,
+		path:    MetricsPath,
+		handler: promhttp.Handler().ServeHTTP,
+	}
+}
+
+func ProfilingRoutes(enableExpVar bool) []*Route {
+	var routes []*Route
+
+	routeFunc := func(path string, handler http.HandlerFunc) *Route {
+		route, _ := NewRoute(http.MethodGet, path, handler)
+		return route
+	}
+
+	routes = append(routes, routeFunc("/debug/pprof/", pprof.Index))
+	routes = append(routes, routeFunc("/debug/pprof/cmdline/", pprof.Cmdline))
+	routes = append(routes, routeFunc("/debug/pprof/profile/", pprof.Profile))
+	routes = append(routes, routeFunc("/debug/pprof/symbol/", pprof.Symbol))
+	routes = append(routes, routeFunc("/debug/pprof/trace/", pprof.Trace))
+	routes = append(routes, routeFunc("/debug/pprof/allocs/", pprof.Handler("allocs").ServeHTTP))
+	routes = append(routes, routeFunc("/debug/pprof/heap/", pprof.Handler("heap").ServeHTTP))
+	routes = append(routes, routeFunc("/debug/pprof/goroutine/", pprof.Handler("goroutine").ServeHTTP))
+	routes = append(routes, routeFunc("/debug/pprof/block/", pprof.Handler("block").ServeHTTP))
+	routes = append(routes, routeFunc("/debug/pprof/threadcreate/", pprof.Handler("threadcreate").ServeHTTP))
+	routes = append(routes, routeFunc("/debug/pprof/mutex/", pprof.Handler("mutex").ServeHTTP))
+	if enableExpVar {
+		routes = append(routes, routeFunc("/debug/vars/", expVars))
+	}
+
+	return routes
+}
+
+// Replicated from expvar.go as not public.
+func expVars(w http.ResponseWriter, _ *http.Request) {
+	first := true
+	w.Header().Set("Content-Type", "application/json")
+	_, _ = fmt.Fprintf(w, "{\n")
+	expvar.Do(func(kv expvar.KeyValue) {
+		if !first {
+			_, _ = fmt.Fprintf(w, ",\n")
+		}
+		first = false
+		_, _ = fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
+	})
+	_, _ = fmt.Fprintf(w, "\n}\n")
+}
diff --git a/component/http/v2/route.go b/component/http/v2/route.go
new file mode 100644
index 000000000..4a721d6c6
--- /dev/null
+++ b/component/http/v2/route.go
@@ -0,0 +1,139 @@
+package v2
+
+import (
+	"errors"
+	"net/http"
+
+	patronhttp "github.com/beatlabs/patron/component/http/middleware"
+	patronerrors "github.com/beatlabs/patron/errors"
+)
+
+// RouteOptionFunc definition for configuring the route in a functional way.
+type RouteOptionFunc func(route *Route) error
+
+// Route definition of an HTTP route.
+type Route struct {
+	method      string
+	path        string
+	handler     http.HandlerFunc
+	middlewares []patronhttp.Func
+}
+
+func (r Route) Method() string {
+	return r.method
+}
+
+func (r Route) Path() string {
+	return r.path
+}
+
+func (r Route) Handler() http.HandlerFunc {
+	return r.handler
+}
+
+func (r Route) Middlewares() []patronhttp.Func {
+	return r.middlewares
+}
+
+func (r Route) String() string {
+	return r.method + " " + r.path
+}
+
+// NewRoute creates a new raw route with functional configuration.
+func NewRoute(method, path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	if method == "" {
+		return nil, errors.New("method is empty")
+	}
+
+	if path == "" {
+		return nil, errors.New("path is empty")
+	}
+
+	if handler == nil {
+		return nil, errors.New("handler is nil")
+	}
+
+	route := &Route{
+		method:  method,
+		path:    path,
+		handler: handler,
+	}
+
+	for _, option := range oo {
+		err := option(route)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return route, nil
+}
+
+// NewGetRoute constructor.
+func NewGetRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodGet, path, handler, oo...)
+}
+
+// NewHeadRoute constructor.
+func NewHeadRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodHead, path, handler, oo...)
+}
+
+// NewPostRoute constructor.
+func NewPostRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodPost, path, handler, oo...)
+}
+
+// NewPutRoute constructor.
+func NewPutRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodPut, path, handler, oo...)
+}
+
+// NewPatchRoute constructor.
+func NewPatchRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodPatch, path, handler, oo...)
+}
+
+// NewDeleteRoute constructor.
+func NewDeleteRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodDelete, path, handler, oo...)
+}
+
+// NewConnectRoute constructor.
+func NewConnectRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodConnect, path, handler, oo...)
+}
+
+// NewOptionsRoute constructor.
+func NewOptionsRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodOptions, path, handler, oo...)
+}
+
+// NewTraceRoute constructor.
+func NewTraceRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodTrace, path, handler, oo...)
+}
+
+// Routes definition.
+type Routes struct {
+	routes []*Route
+	ee     []error
+}
+
+// Append route.
+func (r *Routes) Append(route *Route, err error) {
+	if err != nil {
+		r.ee = append(r.ee, err)
+		return
+	}
+	if route == nil {
+		r.ee = append(r.ee, errors.New("route is nil"))
+		return
+	}
+	r.routes = append(r.routes, route)
+}
+
+// Result of the route aggregation.
+func (r *Routes) Result() ([]*Route, error) {
+	return r.routes, patronerrors.Aggregate(r.ee...)
+}
diff --git a/component/http/v2/route_option.go b/component/http/v2/route_option.go
new file mode 100644
index 000000000..c803b6f49
--- /dev/null
+++ b/component/http/v2/route_option.go
@@ -0,0 +1,58 @@
+package v2
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/beatlabs/patron/cache"
+	"github.com/beatlabs/patron/component/http/auth"
+	httpcache "github.com/beatlabs/patron/component/http/cache"
+	patronhttp "github.com/beatlabs/patron/component/http/middleware"
+	errs "github.com/beatlabs/patron/errors"
+	"golang.org/x/time/rate"
+)
+
+// RateLimiting option for setting a route rate limiter.
+func RateLimiting(limit float64, burst int) RouteOptionFunc {
+	return func(r *Route) error {
+		r.middlewares = append(r.middlewares, patronhttp.NewRateLimiting(rate.NewLimiter(rate.Limit(limit), burst)))
+		return nil
+	}
+}
+
+// Middlewares option for setting the route optionFuncs.
+func Middlewares(mm ...patronhttp.Func) RouteOptionFunc {
+	return func(r *Route) error {
+		if len(mm) == 0 {
+			return errors.New("middlewares are empty")
+		}
+		r.middlewares = append(r.middlewares, mm...)
+		return nil
+	}
+}
+
+// Auth option for setting the route auth.
+func Auth(auth auth.Authenticator) RouteOptionFunc {
+	return func(r *Route) error {
+		if auth == nil {
+			return errors.New("authenticator is nil")
+		}
+		r.middlewares = append(r.middlewares, patronhttp.NewAuth(auth))
+		return nil
+	}
+}
+
+// Cache option for setting the route cache.
+func Cache(cache cache.TTLCache, ageBounds httpcache.Age) RouteOptionFunc {
+	return func(r *Route) error {
+		if r.method != http.MethodGet {
+			return errors.New("cannot apply cache to a route with any method other than GET")
+		}
+		rc, ee := httpcache.NewRouteCache(cache, ageBounds)
+		if len(ee) != 0 {
+			return errs.Aggregate(ee...)
+		}
+		r.middlewares = append(r.middlewares, patronhttp.NewCaching(rc))
+		return nil
+	}
+}
diff --git a/component/http/v2/router/httprouter/route.go b/component/http/v2/router/httprouter/route.go
new file mode 100644
index 000000000..0f5ee9c92
--- /dev/null
+++ b/component/http/v2/router/httprouter/route.go
@@ -0,0 +1,71 @@
+package httprouter
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"os"
+
+	v2 "github.com/beatlabs/patron/component/http/v2"
+	"github.com/julienschmidt/httprouter"
+)
+
+// NewFileServerRoute returns a route that acts as a file server.
+func NewFileServerRoute(path string, assetsDir string, fallbackPath string) (*v2.Route, error) {
+	if path == "" {
+		return nil, errors.New("path is empty")
+	}
+
+	if assetsDir == "" {
+		return nil, errors.New("assets path is empty")
+	}
+
+	if fallbackPath == "" {
+		return nil, errors.New("fallback path is empty")
+	}
+
+	_, err := os.Stat(assetsDir)
+	if os.IsNotExist(err) {
+		return nil, fmt.Errorf("assets directory [%s] doesn't exist", path)
+	} else if err != nil {
+		return nil, fmt.Errorf("error while checking assets dir: %w", err)
+	}
+
+	_, err = os.Stat(fallbackPath)
+	if os.IsNotExist(err) {
+		return nil, fmt.Errorf("fallback file [%s] doesn't exist", fallbackPath)
+	} else if err != nil {
+		return nil, fmt.Errorf("error while checking fallback file: %w", err)
+	}
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		paramPath := ""
+		for _, param := range httprouter.ParamsFromContext(r.Context()) {
+			if param.Key == "path" {
+				paramPath = param.Value
+				break
+			}
+		}
+
+		// get the absolute path to prevent directory traversal
+		path := assetsDir + paramPath
+
+		// check whether a file exists at the given path
+		info, err := os.Stat(path)
+		if os.IsNotExist(err) || info.IsDir() {
+			// file does not exist, serve index.html
+			http.ServeFile(w, r, fallbackPath)
+			return
+		} else if err != nil {
+			// if we got an error (that wasn't that the file doesn't exist) stating the
+			// file, return a 500 internal server error and stop
+			http.Error(w, "", http.StatusInternalServerError)
+			return
+		}
+
+		// otherwise, use server the specific file directly from the filesystem.
+		http.ServeFile(w, r, path)
+	}
+
+	return v2.NewRoute(http.MethodGet, path, handler)
+}
diff --git a/component/http/v2/router/httprouter/router.go b/component/http/v2/router/httprouter/router.go
new file mode 100644
index 000000000..8c8bda594
--- /dev/null
+++ b/component/http/v2/router/httprouter/router.go
@@ -0,0 +1,154 @@
+package httprouter
+
+import (
+	"errors"
+	"fmt"
+	"os"
+
+	"github.com/beatlabs/patron/component/http/middleware"
+	"github.com/beatlabs/patron/component/http/v2"
+	"github.com/beatlabs/patron/log"
+	"github.com/julienschmidt/httprouter"
+)
+
+const defaultDeflateLevel = 6
+
+// OptionFunc definition to allow functional configuration of the router.
+type OptionFunc func(*Config) error
+
+// Config definition.
+type Config struct {
+	aliveCheckFunc        v2.LivenessCheckFunc
+	readyCheckFunc        v2.ReadyCheckFunc
+	deflateLevel          int
+	middlewares           []middleware.Func
+	routes                []*v2.Route
+	enableProfilingExpVar bool
+}
+
+func New(oo ...OptionFunc) (*httprouter.Router, error) {
+	cfg := &Config{
+		aliveCheckFunc: func() v2.AliveStatus { return v2.Alive },
+		readyCheckFunc: func() v2.ReadyStatus { return v2.Ready },
+		deflateLevel:   defaultDeflateLevel,
+	}
+
+	for _, option := range oo {
+		err := option(cfg)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	var stdRoutes []*v2.Route
+
+	mux := httprouter.New()
+	stdRoutes = append(stdRoutes, v2.MetricRoute())
+	stdRoutes = append(stdRoutes, v2.ProfilingRoutes(cfg.enableProfilingExpVar)...)
+
+	route, err := v2.LivenessCheckRoute(cfg.aliveCheckFunc)
+	if err != nil {
+		return nil, err
+	}
+	stdRoutes = append(stdRoutes, route)
+
+	route, err = v2.ReadyCheckRoute(cfg.readyCheckFunc)
+	if err != nil {
+		return nil, err
+	}
+	stdRoutes = append(stdRoutes, route)
+
+	for _, route := range stdRoutes {
+		handler := middleware.Chain(route.Handler(), middleware.NewRecovery())
+		mux.Handler(route.Method(), route.Path(), handler)
+		log.Debugf("added route %s", route)
+	}
+
+	// parse a list of HTTP numeric status codes that must be logged
+	statusCodeLoggerCfg, _ := os.LookupEnv("PATRON_HTTP_STATUS_ERROR_LOGGING")
+	statusCodeLogger, err := middleware.NewStatusCodeLoggerHandler(statusCodeLoggerCfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse status codes %s: %w", statusCodeLoggerCfg, err)
+	}
+
+	for _, route := range cfg.routes {
+		// add standard middlewares
+		middlewares := []middleware.Func{
+			middleware.NewRecovery(),
+			middleware.NewInjectObservability(),
+			middleware.NewLoggingTracing(route.Path(), statusCodeLogger),
+			middleware.NewRequestObserver(route.Method(), route.Path()),
+			middleware.NewCompression(cfg.deflateLevel),
+		}
+		// add router middlewares
+		middlewares = append(middlewares, cfg.middlewares...)
+		// add route middlewares
+		middlewares = append(middlewares, route.Middlewares()...)
+		// chain all middlewares to the handler
+		handler := middleware.Chain(route.Handler(), middlewares...)
+		mux.Handler(route.Method(), route.Path(), handler)
+		log.Debugf("added route %s with %d middlewares", route, len(middlewares))
+	}
+
+	return mux, nil
+}
+
+// Routes option for providing routes to the router.
+func Routes(routes ...*v2.Route) OptionFunc {
+	return func(cfg *Config) error {
+		if len(routes) == 0 {
+			return errors.New("routes are empty")
+		}
+		cfg.routes = routes
+		return nil
+	}
+}
+
+// AliveCheck option for the router.
+func AliveCheck(acf v2.LivenessCheckFunc) OptionFunc {
+	return func(cfg *Config) error {
+		if acf == nil {
+			return errors.New("alive check function is nil")
+		}
+		cfg.aliveCheckFunc = acf
+		return nil
+	}
+}
+
+// ReadyCheck option for the router.
+func ReadyCheck(rcf v2.ReadyCheckFunc) OptionFunc {
+	return func(cfg *Config) error {
+		if rcf == nil {
+			return errors.New("ready check function is nil")
+		}
+		cfg.readyCheckFunc = rcf
+		return nil
+	}
+}
+
+// DeflateLevel option for the compression middleware.
+func DeflateLevel(level int) OptionFunc {
+	return func(cfg *Config) error {
+		cfg.deflateLevel = level
+		return nil
+	}
+}
+
+// Middlewares option for middlewares.
+func Middlewares(mm ...middleware.Func) OptionFunc {
+	return func(cfg *Config) error {
+		if len(mm) == 0 {
+			return errors.New("middlewares are empty")
+		}
+		cfg.middlewares = mm
+		return nil
+	}
+}
+
+// EnableExpVarProfiling option for enabling expVar in profiling endpoints.
+func EnableExpVarProfiling() OptionFunc {
+	return func(cfg *Config) error {
+		cfg.enableProfilingExpVar = true
+		return nil
+	}
+}
diff --git a/component/kafka/kafka.go b/component/kafka/kafka.go
index fdf25c446..011b3e2d7 100644
--- a/component/kafka/kafka.go
+++ b/component/kafka/kafka.go
@@ -101,7 +101,7 @@ func DefaultConsumerSaramaConfig(name string, readCommitted bool) (*sarama.Confi
 	config.Version = sarama.V0_11_0_0
 	if readCommitted {
 		// from Kafka documentation:
-		// Transactions were introduced in Kafka 0.11.0 wherein applications can write to multiple topics and partitions atomically. In order for this to work, consumers reading from these partitions should be configured to only read committed data. This can be achieved by by setting the isolation.level=read_committed in the consumer's configuration.
+		// Transactions were introduced in Kafka 0.11.0 wherein applications can write to multiple topics and partitions atomically. In order for this to work, consumers reading from these partitions should be configured to only read committed data. This can be achieved by setting the isolation.level=read_committed in the consumer's configuration.
 		// In read_committed mode, the consumer will read only those transactional messages which have been successfully committed. It will continue to read non-transactional messages as before. There is no client-side buffering in read_committed mode. Instead, the end offset of a partition for a read_committed consumer would be the offset of the first message in the partition belonging to an open transaction. This offset is known as the 'Last Stable Offset'(LSO).
 		config.Consumer.IsolationLevel = sarama.ReadCommitted
 	}
diff --git a/component/sqs/option.go b/component/sqs/option.go
index 0143b6cc3..7a42f4c43 100644
--- a/component/sqs/option.go
+++ b/component/sqs/option.go
@@ -38,9 +38,9 @@ func PollWaitSeconds(pollWaitSeconds int64) OptionFunc {
 }
 
 // VisibilityTimeout sets the time a message is invisible after it has been requested.
-// This is a built in resiliency mechanism so that, should the consumer fail to acknowledge the message within such timeout,
+// This is a built-in resiliency mechanism so that, should the consumer fail to acknowledge the message within such timeout,
 // it will become visible again and thus available for retries.
-// Allowed values are between 0 and and 12 hours in seconds.
+// Allowed values are between 0 and 12 hours in seconds.
 func VisibilityTimeout(visibilityTimeout int64) OptionFunc {
 	return func(c *Component) error {
 		if visibilityTimeout < 0 || visibilityTimeout > twelveHoursInSeconds {
diff --git a/correlation/correlation.go b/correlation/correlation.go
index b70b741f2..65e614ece 100644
--- a/correlation/correlation.go
+++ b/correlation/correlation.go
@@ -3,6 +3,7 @@ package correlation
 
 import (
 	"context"
+	"net/http"
 
 	"github.com/google/uuid"
 )
@@ -31,3 +32,23 @@ func IDFromContext(ctx context.Context) string {
 func ContextWithID(ctx context.Context, correlationID string) context.Context {
 	return context.WithValue(ctx, idKey, correlationID)
 }
+
+func GetOrSetHeaderID(h http.Header) string {
+	cor, ok := h[HeaderID]
+	if !ok {
+		corID := uuid.New().String()
+		h.Set(HeaderID, corID)
+		return corID
+	}
+	if len(cor) == 0 {
+		corID := uuid.New().String()
+		h.Set(HeaderID, corID)
+		return corID
+	}
+	if cor[0] == "" {
+		corID := uuid.New().String()
+		h.Set(HeaderID, corID)
+		return corID
+	}
+	return cor[0]
+}
diff --git a/doc.go b/doc.go
index 5febce704..6b57f64d0 100644
--- a/doc.go
+++ b/doc.go
@@ -7,15 +7,15 @@ Patron is french for template or pattern, but it means also boss which we found
 
 The entry point of the framework is the Service.
 The Service uses Components to handle the processing of sync and async requests.
-The Service can setup as many Components it wants, even multiple HTTP components provided the port does not collide.
-The Service starts by default a HTTP component which hosts the debug, alive, ready and metric endpoints.
+The Service can set up as many Components it wants, even multiple HTTP components provided the port does not collide.
+The Service starts by default an HTTP component which hosts the debug, alive, ready and metric endpoints.
 Any other endpoints will be added to the default HTTP Component as Routes.
 The service set's up by default logging with zerolog, tracing and metrics with jaeger and prometheus.
 
 Patron provides abstractions for the following functionality of the framework:
 
   - service, which orchestrates everything
-  - components and processors, which provide a abstraction of adding processing functionality to the service
+  - components and processors, which provide an abstraction of adding processing functionality to the service
   	- asynchronous message processing (RabbitMQ, Kafka)
   	- synchronous processing (HTTP)
   - metrics and tracing
@@ -23,6 +23,6 @@ Patron provides abstractions for the following functionality of the framework:
   - configuration management
 
 Patron provides same defaults for making the usage as simple as possible.
-For more details please check out github repository https://github.com/beatlabs/patron.
+For more details please check out the GitHub repository https://github.com/beatlabs/patron.
 */
 package patron
diff --git a/docs/Architecture.md b/docs/Architecture.md
index 51beb79aa..9706f669a 100644
--- a/docs/Architecture.md
+++ b/docs/Architecture.md
@@ -54,3 +54,15 @@ The service has some default settings which can be changed via environment varia
   - sampler type `probabilistic`with `PATRON_JAEGER_SAMPLER_TYPE`
   - sampler param `0.0` with `PATRON_JAEGER_SAMPLER_PARAM`, which means that no traces are sent.
   
+
+The service provides also the option to bypass the legacy created HTTP component and use the new v2 component.
+This will effectively disable the default legacy HTTP component.
+
+```go
+err = service.WithRouter(router).WithSIGHUP(sig).Run(ctx)
+    if err != nil {
+    log.Fatalf("failed to create and run service %v", err)
+}
+```
+
+This above builder extension is temporary until we fully replace the legacy HTTP component with our v2 component.
\ No newline at end of file
diff --git a/docs/Examples.md b/docs/Examples.md
index 376dac2da..bb9f65ad2 100644
--- a/docs/Examples.md
+++ b/docs/Examples.md
@@ -170,7 +170,7 @@ The service can be started as follows:
 go run examples/grpc/main.go
 ```
 
-## All of the above working together
+## All the above working together
 
 After all services have been started successfully we can send a request and see how it travels through all of them by running. 
 
diff --git a/docs/SIGNYOURWORK.md b/docs/SIGNYOURWORK.md
index 604c1343d..9f5318ad1 100644
--- a/docs/SIGNYOURWORK.md
+++ b/docs/SIGNYOURWORK.md
@@ -51,7 +51,7 @@ then you just add a line to every git commit message:
 
 using your real name (sorry, no pseudonyms or anonymous contributions.)
 
-You can add the sign off when creating the git commit via `git commit -s`.
+You can add the sign-off when creating the git commit via `git commit -s`.
 
 If you want this to be automatic you can set up some aliases:
 
diff --git a/docs/components/HTTP.md b/docs/components/HTTP.md
index b77594465..2d5a3ee3d 100644
--- a/docs/components/HTTP.md
+++ b/docs/components/HTTP.md
@@ -47,7 +47,7 @@ func (cb *Builder) WithReadTimeout(rt time.Duration) *Builder {
 	// ...
 }
 
-// WithWriteTimeout sets the Write Timeout for the HTTP component.
+// WithWriteTimeout sets the write timeout for the HTTP component.
 func (cb *Builder) WithWriteTimeout(wt time.Duration) *Builder {
 	// ...
 }
@@ -118,7 +118,7 @@ You can create new middleware functions and pass them to Service to be chained o
 ```go
 type MiddlewareFunc func(next http.Handler) http.Handler
 
-// Setup a simple middleware for CORS
+// Set up a simple middleware for CORS
 newMiddleware := func(h http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
         w.Header().Add("Access-Control-Allow-Origin", "*")
@@ -499,13 +499,13 @@ The http cache exposes several metrics, used to
 - help trim the optimal time-to-live policy
 - identify client control interference
 
-By default we are using prometheus as the the pre-defined metrics framework.
+By default, we are using prometheus as the pre-defined metrics framework.
 
 - `additions = misses + evictions`
 
 Always , the cache addition operations (objects added to the cache), 
 must be equal to the misses (requests that were not cached) plus the evictions (expired objects).
-Otherwise we would expect to notice also an increased amount of errors or having the cache misbehaving in a different manner.
+Otherwise, we would expect to notice also an increased amount of errors or having the cache misbehaving in a different manner.
 
 - `additions ~ misses`
 
@@ -522,7 +522,7 @@ this would signify that probably a cache is not a good option for the access pat
 - `eviction age`
 
 The age at which the objects are evicted from the cache is a very useful indicator. 
-If the vast amount of evictions are close to the time to live setting, it would indicate a nicely working cache.
+If the vast amount of evictions is close to the time to live setting, it would indicate a nicely working cache.
 If we find that many evictions happen before the time to live threshold, clients would be making use cache-control headers.
  
 
diff --git a/docs/components/HTTPv2.md b/docs/components/HTTPv2.md
new file mode 100644
index 000000000..24855c7ae
--- /dev/null
+++ b/docs/components/HTTPv2.md
@@ -0,0 +1,34 @@
+# HTTP v2
+
+HTTP v2 tries to create a very thin layer around any http router provided by the end user.  
+Patron provides an implementation for the simple and excellent [httprouter](https://github.com/julienschmidt/httprouter) which is 
+available in the `httprouter` available in Patron.  
+Because the HTTP component relies on the standard Go `http.Handler` any implementation can be provided as long as the interface is implemented.  
+The component is responsible for running the HTTP server using the handler and terminating on request.
+
+The component provides out of the box:
+
+- HTTP lifecycle endpoints (liveness and readiness)
+- metrics and distributed traces
+- profiling using the standard `net/http/pprof` package
+
+The [example](../../examples) folder contains various use cases.
+
+The component create is then passed to the functional option of the Patron services, which replaces the default legacy HTTP handler
+with the new HTTP handler.
+
+## httprouter
+
+The implementation provides the following:
+
+- file server route for helping us serving files e.g. SPA
+- functional options to set up the handler e.g. live and readiness checks, middlewares, routes, compression, etc.
+
+In the handler creation process are adding automatically to every route our standard middlewares that handle:
+
+- recovery from panics
+- configurable logging
+- tracing
+- metrics
+- compression
+
diff --git a/docs/components/gRPC.md b/docs/components/gRPC.md
index f8c074be3..6275f4d84 100644
--- a/docs/components/gRPC.md
+++ b/docs/components/gRPC.md
@@ -7,7 +7,7 @@ As the server implements the Patron `component` interface, it also handles grace
 
 Setting up a gRPC component is done via the Builder (which follows the builder pattern), and supports various configuration values in the form of the `grpc.ServerOption` struct during setup.
 
-Check out the [examples/](/examples) folder for an hands-on tutorial on setting up a server and working with gRPC in Patron.
+Check out the [examples/](/examples) folder for a hands-on tutorial on setting up a server and working with gRPC in Patron.
 
 ## Metrics
 
diff --git a/docs/other/Reliability.md b/docs/other/Reliability.md
index 8b2fe2de8..2a42c37a6 100644
--- a/docs/other/Reliability.md
+++ b/docs/other/Reliability.md
@@ -7,7 +7,7 @@ The reliability package contains the following implementations:
 
 ## Circuit Breaker Pattern
 
-The circuit breaker supports a half-open state which allows to probe for successful responses in order to close the circuit again.  
+The circuit breaker supports a half-open state which allows probing for successful responses in order to close the circuit again.  
 Every aspect of the circuit breaker is configurable via its settings.
 
 The action to be executed has the signature type of:
diff --git a/examples/check/main.go b/examples/check/main.go
index 064a70d30..189873219 100644
--- a/examples/check/main.go
+++ b/examples/check/main.go
@@ -13,7 +13,7 @@ import (
 // this program is meant to be used to check the whole 'examples' cluster. It's meant to be put in the CI
 // so that new developments do not break the examples. We can see this as a single E2E test check
 // the program is supposed to start a transaction that flows through all the services under the 'examples'
-// folder and than check that the transaction actually triggered all the services in the chain until the
+// folder and then check that the transaction actually triggered all the services in the chain until the
 // 'leaves'
 func main() {
 	// create simple client with timeout set to 1 second
@@ -22,7 +22,7 @@ func main() {
 	}
 
 	// perform a client call that is supposed to trigger a call across all the services
-	var jsonStr = []byte(`{"firstname":"John","lastname":"Doe"}`)
+	jsonStr := []byte(`{"firstname":"John","lastname":"Doe"}`)
 	req, err := http.NewRequest("POST", "http://localhost:50000/api", bytes.NewBuffer(jsonStr))
 	if err != nil {
 		log.Fatal(err)
@@ -37,7 +37,8 @@ func main() {
 		if err != nil {
 			log.Fatal(err)
 		}
-		log.Fatalf("Response supposed to be status code %d instead it is %d (body: %q)", http.StatusCreated, resp.StatusCode, string(body))
+		log.Fatalf("Response supposed to be status code %d instead it is %d (body: %q)",
+			http.StatusCreated, resp.StatusCode, string(body))
 	}
 
 	// wait 1 second so that metrics can be flushed to http endpoint
@@ -73,9 +74,9 @@ func main() {
 		responses[svc] = rsp
 	}
 
-	// To check that the message flowed across all of the services we check the leaves of the message flow.
+	// To check that the message flowed across all the services we check the leaves of the message flow.
 	// The leaves are 'grpc-svc' and 'http-cache-svc'
-	// To check that the message made through them we check that the metrics endpoint contain certain strings
+	// To check that the message made through them, we check that the metrics endpoint contain certain strings
 	// by using a regex
 
 	// services to url map
diff --git a/examples/compression-middleware/main.go b/examples/compression-middleware/main.go
index 5fbf125b8..7c441c601 100644
--- a/examples/compression-middleware/main.go
+++ b/examples/compression-middleware/main.go
@@ -4,11 +4,13 @@ import (
 	"context"
 	"fmt"
 	"math/rand"
+	"net/http"
 	"os"
 	"time"
 
 	"github.com/beatlabs/patron"
-	patronhttp "github.com/beatlabs/patron/component/http"
+	v2 "github.com/beatlabs/patron/component/http/v2"
+	"github.com/beatlabs/patron/component/http/v2/router/httprouter"
 	"github.com/beatlabs/patron/log"
 )
 
@@ -49,33 +51,43 @@ func main() {
 	service, err := patron.New(name, version)
 	handle(err)
 
-	routesBuilder := patronhttp.NewRoutesBuilder().
-		Append(patronhttp.NewRouteBuilder("/foo", rnd).MethodGet()).
-		Append(patronhttp.NewRouteBuilder("/bar", rnd).MethodGet()).
-		Append(patronhttp.NewRouteBuilder("/hello", hello).MethodGet())
+	var routes v2.Routes
+	routes.Append(v2.NewGetRoute("/foo", rnd))
+	routes.Append(v2.NewGetRoute("/bar", rnd))
+	routes.Append(v2.NewGetRoute("/hello", hello))
+	rr, err := routes.Result()
+	if err != nil {
+		log.Fatalf("failed to create routes: %v", err)
+	}
+
+	router, err := httprouter.New(httprouter.Routes(rr...))
+	if err != nil {
+		log.Fatalf("failed to create http router: %v", err)
+	}
 
 	ctx := context.Background()
-	err = service.
-		WithRoutesBuilder(routesBuilder).
-		WithUncompressedPaths("/bar").
-		Run(ctx)
+	err = service.WithRouter(router).Run(ctx)
 	handle(err)
 }
 
-// creates some random data to send back
-func rnd(_ context.Context, _ *patronhttp.Request) (*patronhttp.Response, error) {
+func rnd(rw http.ResponseWriter, _ *http.Request) {
 	rand.Seed(time.Now().UnixNano())
 	data := make([]byte, 1<<20)
 	_, err := rand.Read(data)
 	if err != nil {
-		return nil, err
+		rw.WriteHeader(http.StatusInternalServerError)
+		return
 	}
 
-	return patronhttp.NewResponse(data), nil
+	rw.WriteHeader(http.StatusCreated)
+	_, _ = rw.Write(data)
+	return
 }
 
-func hello(_ context.Context, _ *patronhttp.Request) (*patronhttp.Response, error) {
-	return patronhttp.NewResponse("hello!"), nil
+func hello(rw http.ResponseWriter, _ *http.Request) {
+	rw.WriteHeader(http.StatusCreated)
+	_, _ = rw.Write([]byte("hello!"))
+	return
 }
 
 func handle(err error) {
diff --git a/examples/http-cache/main.go b/examples/http-cache/main.go
index 56be52755..cd4a64f33 100644
--- a/examples/http-cache/main.go
+++ b/examples/http-cache/main.go
@@ -4,13 +4,15 @@ import (
 	"context"
 	"fmt"
 	"log"
+	"net/http"
 	"os"
 	"time"
 
 	"github.com/beatlabs/patron"
 	"github.com/beatlabs/patron/cache/redis"
-	"github.com/beatlabs/patron/component/http"
 	httpcache "github.com/beatlabs/patron/component/http/cache"
+	v2 "github.com/beatlabs/patron/component/http/v2"
+	"github.com/beatlabs/patron/component/http/v2/router/httprouter"
 )
 
 func init() {
@@ -49,34 +51,40 @@ func main() {
 		os.Exit(1)
 	}
 
-	routesBuilder := http.NewRoutesBuilder().
-		Append(http.NewGetRouteBuilder("/", handler).
-			WithRouteCache(cache, httpcache.Age{
-				// we wont allow to override the cache more than once per 15 seconds
-				Min: 15 * time.Second,
-				// by default we might send stale response for up to 1 minute
-				Max: 60 * time.Second,
-			}))
+	var routes v2.Routes
+	routes.Append(v2.NewGetRoute("/", handler, v2.Cache(cache, httpcache.Age{
+		// we won't allow to override the cache more than once per 15 seconds
+		Min: 15 * time.Second,
+		// by default, we might send stale response for up to 1 minute
+		Max: 60 * time.Second,
+	})))
+	rr, err := routes.Result()
+	if err != nil {
+		log.Fatalf("failed to create routes: %v", err)
+	}
+
+	router, err := httprouter.New(httprouter.Routes(rr...))
+	if err != nil {
+		log.Fatalf("failed to create http router: %v", err)
+	}
 
 	sig := func() {
 		fmt.Println("exit gracefully...")
 		os.Exit(0)
 	}
 
-	err = service.
-		WithRoutesBuilder(routesBuilder).
-		WithSIGHUP(sig).
-		Run(ctx)
+	err = service.WithRouter(router).WithSIGHUP(sig).Run(ctx)
 	if err != nil {
 		log.Fatalf("failed to create and run service %v", err)
 	}
 }
 
-// handler gives the 7 minute interval of the current unix timestamp
+// handler gives the 7-minute interval of the current unix timestamp
 // since the response will be the same for the next 7 minutes, it s a good use-case to apply caching
-func handler(_ context.Context, _ *http.Request) (*http.Response, error) {
+func handler(rw http.ResponseWriter, _ *http.Request) {
 	now := time.Now()
 	minutes := now.Unix() / 60
 	minuteInterval := minutes / 7
-	return http.NewResponse(fmt.Sprintf("current unix 7-minute interval is (%d) called at %v", minuteInterval, now.Unix())), nil
+	rw.WriteHeader(http.StatusOK)
+	_, _ = rw.Write([]byte(fmt.Sprintf("current unix 7-minute interval is (%d) called at %v", minuteInterval, now.Unix())))
 }
diff --git a/examples/http-sec/main.go b/examples/http-sec/main.go
index 25008b656..c79d87e2e 100644
--- a/examples/http-sec/main.go
+++ b/examples/http-sec/main.go
@@ -10,11 +10,13 @@ import (
 	"github.com/Shopify/sarama"
 	"github.com/beatlabs/patron"
 	clienthttp "github.com/beatlabs/patron/client/http"
-	v2 "github.com/beatlabs/patron/client/kafka/v2"
-	patronhttp "github.com/beatlabs/patron/component/http"
+	patronkafka "github.com/beatlabs/patron/client/kafka/v2"
 	"github.com/beatlabs/patron/component/http/auth/apikey"
+	v2 "github.com/beatlabs/patron/component/http/v2"
+	"github.com/beatlabs/patron/component/http/v2/router/httprouter"
 	"github.com/beatlabs/patron/component/kafka"
 	"github.com/beatlabs/patron/encoding/json"
+	"github.com/beatlabs/patron/encoding/protobuf"
 	"github.com/beatlabs/patron/examples"
 	"github.com/beatlabs/patron/log"
 )
@@ -63,18 +65,27 @@ func main() {
 		log.Fatalf("failed to create authenticator %v", err)
 	}
 
-	routesBuilder := patronhttp.NewRoutesBuilder().
-		Append(patronhttp.NewGetRouteBuilder("/", asyncComp.forwardToKafkaHandler).WithTrace().WithAuth(auth))
+	var routes v2.Routes
+	routes.Append(v2.NewGetRoute("/", asyncComp.forwardToKafkaHandler, v2.Auth(auth)))
+	rr, err := routes.Result()
+	if err != nil {
+		log.Fatalf("failed to create routes: %v", err)
+	}
+
+	router, err := httprouter.New(httprouter.Routes(rr...))
+	if err != nil {
+		log.Fatalf("failed to create http router: %v", err)
+	}
 
 	ctx := context.Background()
-	err = service.WithRoutesBuilder(routesBuilder).Run(ctx)
+	err = service.WithRouter(router).Run(ctx)
 	if err != nil {
 		log.Fatalf("failed to create and run service %v", err)
 	}
 }
 
 type kafkaProducer struct {
-	prd   *v2.AsyncProducer
+	prd   *patronkafka.AsyncProducer
 	topic string
 }
 
@@ -85,7 +96,7 @@ func newAsyncKafkaProducer(kafkaBroker, topic string, readCommitted bool) (*kafk
 		return nil, err
 	}
 
-	prd, chErr, err := v2.New([]string{kafkaBroker}, saramaCfg).CreateAsync()
+	prd, chErr, err := patronkafka.New([]string{kafkaBroker}, saramaCfg).CreateAsync()
 	if err != nil {
 		return nil, err
 	}
@@ -98,31 +109,41 @@ func newAsyncKafkaProducer(kafkaBroker, topic string, readCommitted bool) (*kafk
 	return &kafkaProducer{prd: prd, topic: topic}, nil
 }
 
-// forwardToKafkaHandler is an http handler that decodes the input request and
+// forwardToKafkaHandler is a http handler that decodes the input request and
 // publishes the decoded content as a message into a kafka topic (also does an HTTP GET request to google.com)
-func (hc *kafkaProducer) forwardToKafkaHandler(ctx context.Context, req *patronhttp.Request) (*patronhttp.Response, error) {
+func (hc *kafkaProducer) forwardToKafkaHandler(rw http.ResponseWriter, r *http.Request) {
 	var u examples.User
-	err := req.Decode(&u)
+
+	err := protobuf.Decode(r.Body, &u)
 	if err != nil {
-		return nil, fmt.Errorf("failed to decode message: %w", err)
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed to decode request: %v", err)))
+		return
 	}
 
 	googleReq, err := http.NewRequest("GET", "https://www.google.com", nil)
 	if err != nil {
-		return nil, fmt.Errorf("failed to create request for www.google.com: %w", err)
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed to create request for www.google.com: %v", err)))
+		return
 	}
 	cl, err := clienthttp.New(clienthttp.Timeout(5 * time.Second))
 	if err != nil {
-		return nil, err
+		rw.WriteHeader(http.StatusInternalServerError)
+		return
 	}
 	_, err = cl.Do(googleReq)
 	if err != nil {
-		return nil, fmt.Errorf("failed to get www.google.com: %w", err)
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed to get www.google.com: %v", err)))
+		return
 	}
 
 	b, err := json.Encode(&u)
 	if err != nil {
-		return nil, fmt.Errorf("failed to encode message: %w", err)
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed to encode message: %v", err)))
+		return
 	}
 
 	kafkaMsg := &sarama.ProducerMessage{
@@ -130,13 +151,14 @@ func (hc *kafkaProducer) forwardToKafkaHandler(ctx context.Context, req *patronh
 		Value: sarama.ByteEncoder(b),
 	}
 
-	err = hc.prd.Send(ctx, kafkaMsg)
+	err = hc.prd.Send(r.Context(), kafkaMsg)
 	if err != nil {
-		return nil, err
+		rw.WriteHeader(http.StatusInternalServerError)
+		return
 	}
 
-	log.FromContext(ctx).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
-	return nil, nil
+	log.FromContext(r.Context()).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
+	rw.WriteHeader(http.StatusCreated)
 }
 
 type apiKeyValidator struct {
diff --git a/examples/http/main.go b/examples/http/main.go
index 7e0ebbf61..2748b3c4d 100644
--- a/examples/http/main.go
+++ b/examples/http/main.go
@@ -13,7 +13,9 @@ import (
 
 	"github.com/beatlabs/patron"
 	clienthttp "github.com/beatlabs/patron/client/http"
-	patronhttp "github.com/beatlabs/patron/component/http"
+	v2 "github.com/beatlabs/patron/component/http/v2"
+	"github.com/beatlabs/patron/component/http/v2/router/httprouter"
+	"github.com/beatlabs/patron/encoding/json"
 	"github.com/beatlabs/patron/encoding/protobuf"
 	"github.com/beatlabs/patron/examples"
 	"github.com/beatlabs/patron/log"
@@ -34,7 +36,7 @@ func init() {
 		fmt.Printf("failed to set sampler env vars: %v", err)
 		os.Exit(1)
 	}
-	// allows to run from any folder the 'go run examples/http/main.go'
+	// allows running from any folder the 'go run examples/http/main.go'
 	var ok bool
 	assetsFolder, ok = os.LookupEnv("PATRON_EXAMPLE_ASSETS_FOLDER")
 	if !ok {
@@ -58,13 +60,8 @@ func main() {
 		os.Exit(1)
 	}
 
-	routesBuilder := patronhttp.NewRoutesBuilder().
-		Append(patronhttp.NewFileServer("/frontend/*path", assetsFolder, assetsFolder+"/index.html")).
-		Append(patronhttp.NewPostRouteBuilder("/api", httpHandler)).
-		Append(patronhttp.NewGetRouteBuilder("/api", getHandler).WithRateLimiting(50, 50))
-
-	// Setup a simple CORS middleware
-	middlewareCors := func(h http.Handler) http.Handler {
+	// Set up a simple CORS middleware
+	corsMiddleware := func(h http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			w.Header().Add("Access-Control-Allow-Origin", "*")
 			w.Header().Add("Access-Control-Allow-Methods", "GET, POST")
@@ -73,32 +70,45 @@ func main() {
 			h.ServeHTTP(w, r)
 		})
 	}
+
+	var routes v2.Routes
+	routes.Append(v2.NewGetRoute("/api", getHandler, v2.RateLimiting(50, 50)))
+	routes.Append(v2.NewPostRoute("/api", httpHandler))
+	routes.Append(httprouter.NewFileServerRoute("/frontend/*path", assetsFolder, assetsFolder+"/index.html"))
+	rr, err := routes.Result()
+	if err != nil {
+		log.Fatalf("failed to create routes: %v", err)
+	}
+
+	router, err := httprouter.New(httprouter.Middlewares(corsMiddleware), httprouter.Routes(rr...))
+	if err != nil {
+		log.Fatalf("failed to create http router: %v", err)
+	}
+
 	sig := func() {
 		log.Info("exit gracefully...")
 		os.Exit(0)
 	}
 
 	ctx := context.Background()
-	err = service.
-		WithRoutesBuilder(routesBuilder).
-		WithMiddlewares(middlewareCors).
-		WithSIGHUP(sig).
-		Run(ctx)
+	err = service.WithRouter(router).WithSIGHUP(sig).Run(ctx)
 	if err != nil {
 		log.Fatalf("failed to create and run service %v", err)
 	}
 }
 
-func getHandler(_ context.Context, _ *patronhttp.Request) (*patronhttp.Response, error) {
-	return patronhttp.NewResponse(fmt.Sprint("Testing Middleware", http.StatusOK)), nil
+func getHandler(rw http.ResponseWriter, _ *http.Request) {
+	rw.WriteHeader(http.StatusOK)
+	_, _ = rw.Write([]byte("Testing Middleware"))
 }
 
-// httpHandler proxies the inbound JSON HTTP request to a protobuf HTTP request
-func httpHandler(ctx context.Context, req *patronhttp.Request) (*patronhttp.Response, error) {
+func httpHandler(rw http.ResponseWriter, r *http.Request) {
 	now := time.Now().Unix()
 	if requestsCount <= 0 && refreshAfter > now {
-		return nil, patronhttp.NewErrorWithCodeAndPayload(http.StatusTooManyRequests, "no more requests and the refresh time is in the future").
-			WithHeaders(map[string]string{"Retry-After": strconv.Itoa(int(refreshAfter))})
+		rw.WriteHeader(http.StatusTooManyRequests)
+		rw.Header().Set("Retry-After", strconv.Itoa(int(refreshAfter)))
+		_, _ = rw.Write([]byte("no more requests and the refresh time is in the future"))
+		return
 	}
 
 	requestsCount--
@@ -108,40 +118,51 @@ func httpHandler(ctx context.Context, req *patronhttp.Request) (*patronhttp.Resp
 
 	interval, err := DoIntervalRequest()
 	if err != nil {
-		log.FromContext(ctx).Infof("httpHandler: failed to get interval information %v: could it be that the http-cache service is not running ?", err)
+		log.FromContext(r.Context()).Infof("httpHandler: failed to get interval information %v: could it be that the http-cache service is not running ?", err)
 	} else {
-		log.FromContext(ctx).Infof("httpHandler: pipeline initiated at: %s", interval)
+		log.FromContext(r.Context()).Infof("httpHandler: pipeline initiated at: %s", interval)
 	}
 
 	var u examples.User
 
-	err = req.Decode(&u)
+	err = json.Decode(r.Body, &u)
 	if err != nil {
-		return nil, patronhttp.NewValidationErrorWithPayload(fmt.Sprintf("failed to decode request: %v", err))
+		rw.WriteHeader(http.StatusBadRequest)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed to decode request: %v", err)))
+		return
 	}
 
 	b, err := protobuf.Encode(&u)
 	if err != nil {
-		return nil, patronhttp.NewErrorWithCodeAndPayload(http.StatusInternalServerError, fmt.Sprintf("failed create request: %v", err))
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed create request: %v", err)))
+		return
 	}
 
 	httpRequest, err := http.NewRequest("GET", "http://localhost:50001", bytes.NewReader(b))
 	if err != nil {
-		return nil, patronhttp.NewErrorWithCodeAndPayload(http.StatusInternalServerError, fmt.Sprintf("failed create request: %v", err))
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed create request: %v", err)))
+		return
 	}
 	httpRequest.Header.Add("Content-Type", protobuf.Type)
 	httpRequest.Header.Add("Accept", protobuf.Type)
 	httpRequest.Header.Add("Authorization", "Apikey 123456")
 	cl, err := clienthttp.New(clienthttp.Timeout(5 * time.Second))
 	if err != nil {
-		return nil, err
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed execute request: %v", err)))
+		return
 	}
 	rsp, err := cl.Do(httpRequest)
 	if err != nil {
-		return nil, patronhttp.NewErrorWithCodeAndPayload(http.StatusInternalServerError, fmt.Sprintf("failed to perform http request with protobuf payload: %v", err))
+		rw.WriteHeader(http.StatusInternalServerError)
+		_, _ = rw.Write([]byte(fmt.Sprintf("failed to perform http request with protobuf payload: %v", err)))
+		return
 	}
-	log.FromContext(ctx).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
-	return patronhttp.NewResponse(fmt.Sprintf("got %s from HTTP route", rsp.Status)), nil
+	log.FromContext(r.Context()).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
+	rw.WriteHeader(http.StatusCreated)
+	_, _ = rw.Write([]byte(fmt.Sprintf("got %s from HTTP route", rsp.Status)))
 }
 
 // DoIntervalRequest is a helper method to make a request to the http-cache example service from other examples
diff --git a/examples/kafka-legacy/main.go b/examples/kafka-legacy/main.go
index 504cd65ab..b0a801411 100644
--- a/examples/kafka-legacy/main.go
+++ b/examples/kafka-legacy/main.go
@@ -97,10 +97,8 @@ func newKafkaComponent(name, broker, topic, groupID string, publisher *patronamq
 		return nil, err
 	}
 
-	cmp, err := async.New("kafka-cmp", cf, kafkaCmp.Process).
-		WithRetries(10).
-		WithRetryWait(5 * time.Second).
-		Create()
+	cmp, err := async.New("kafka-cmp", cf, kafkaCmp.Process).WithRetries(10).
+		WithRetryWait(5 * time.Second).Create()
 	if err != nil {
 		return nil, err
 	}
diff --git a/examples/kafka/main.go b/examples/kafka/main.go
index 877b15d2f..45381ca5c 100644
--- a/examples/kafka/main.go
+++ b/examples/kafka/main.go
@@ -99,19 +99,9 @@ func newKafkaComponent(name, broker, topic, groupID string, publisher *patronamq
 	saramaCfg.Net.DialTimeout = 15 * time.Second
 	saramaCfg.Version = sarama.V2_6_0_0
 
-	cmp, err := group.New(
-		name,
-		groupID,
-		[]string{broker},
-		[]string{topic},
-		kafkaCmp.Process,
-		saramaCfg,
-		group.FailureStrategy(kafka.SkipStrategy),
-		group.BatchSize(1),
-		group.BatchTimeout(1*time.Second),
-		group.Retries(10),
-		group.RetryWait(3*time.Second),
-		group.CommitSync())
+	cmp, err := group.New(name, groupID, []string{broker}, []string{topic}, kafkaCmp.Process, saramaCfg,
+		group.FailureStrategy(kafka.SkipStrategy), group.BatchSize(1), group.BatchTimeout(1*time.Second),
+		group.Retries(10), group.RetryWait(3*time.Second), group.CommitSync())
 	if err != nil {
 		return nil, err
 	}
diff --git a/examples/sqs-simple/main.go b/examples/sqs-simple/main.go
index b110fa713..e7eae2d1e 100644
--- a/examples/sqs-simple/main.go
+++ b/examples/sqs-simple/main.go
@@ -93,7 +93,7 @@ func sampleSqs() (*async.Component, error) {
 	// The max number of retires of a message is determined by the SQS queue, not the consumer.
 	return async.New("sqs", factory, messageHandler).
 		// Note that NackExitStrategy does not work with concurrency, so we need to pick either Nack or Ack Strategy
-		// Ack strategy is not recommended for SQS: we want failed messages to end up in the dead letter queue
+		// is not recommended for SQS: we want failed messages to end up in the dead letter queue
 		WithFailureStrategy(async.NackStrategy).
 		WithRetries(3).
 		WithRetryWait(30 * time.Second).
diff --git a/log/std/logger.go b/log/std/logger.go
index e37ff7af5..7a848be19 100644
--- a/log/std/logger.go
+++ b/log/std/logger.go
@@ -26,6 +26,7 @@ type Logger struct {
 	level      patronLog.Level
 	fields     map[string]interface{}
 	fieldsLine string
+	out        io.Writer
 	debug      *log.Logger
 	info       *log.Logger
 	warn       *log.Logger
@@ -53,6 +54,7 @@ func NewWithFlags(out io.Writer, lvl patronLog.Level, fields map[string]interfac
 		level:      lvl,
 		fields:     fields,
 		fieldsLine: fieldsLine,
+		out:        out,
 	}
 }
 
@@ -92,11 +94,7 @@ func (l *Logger) Sub(fields map[string]interface{}) patronLog.Logger {
 		fields[key] = value
 	}
 
-	newLogger := *l
-	newLogger.fields = fields
-	newLogger.fieldsLine = createFieldsLine(fields)
-
-	return &newLogger
+	return New(l.out, l.level, fields)
 }
 
 // Fatal logging.
diff --git a/service.go b/service.go
index 554a3aea5..a5bca2690 100644
--- a/service.go
+++ b/service.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"net/http"
 	"os"
 	"os/signal"
 	"strconv"
@@ -12,7 +13,9 @@ import (
 	"syscall"
 	"time"
 
-	"github.com/beatlabs/patron/component/http"
+	patronhttp "github.com/beatlabs/patron/component/http"
+	"github.com/beatlabs/patron/component/http/middleware"
+	v2 "github.com/beatlabs/patron/component/http/v2"
 	patronErrors "github.com/beatlabs/patron/errors"
 	"github.com/beatlabs/patron/log"
 	"github.com/beatlabs/patron/log/std"
@@ -33,17 +36,18 @@ type Component interface {
 }
 
 // service is responsible for managing and setting up everything.
-// The service will start by default a HTTP component in order to host management endpoint.
+// The service will start by default an HTTP component in order to host management endpoint.
 type service struct {
 	name              string
 	cps               []Component
-	routesBuilder     *http.RoutesBuilder
-	middlewares       []http.MiddlewareFunc
-	acf               http.AliveCheckFunc
-	rcf               http.ReadyCheckFunc
+	routesBuilder     *patronhttp.RoutesBuilder
+	middlewares       []middleware.Func
+	acf               patronhttp.AliveCheckFunc
+	rcf               patronhttp.ReadyCheckFunc
 	termSig           chan os.Signal
 	sighupHandler     func()
 	uncompressedPaths []string
+	httpRouter        http.Handler
 }
 
 func (s *service) setupOSSignal() {
@@ -87,44 +91,84 @@ func (s *service) createHTTPComponent() (Component, error) {
 	portVal := int64(50000)
 	port, ok := os.LookupEnv("PATRON_HTTP_DEFAULT_PORT")
 	if ok {
-		portVal, err = strconv.ParseInt(port, 10, 64)
+		portVal, err = strconv.ParseInt(port, 10, 32)
 		if err != nil {
 			return nil, fmt.Errorf("env var for HTTP default port is not valid: %w", err)
 		}
 	}
-	port = strconv.FormatInt(portVal, 10)
-	log.Debugf("creating default HTTP component at port %s", port)
+	log.Debugf("creating default HTTP component at port %d", portVal)
 
-	b := http.NewBuilder().WithPort(int(portVal))
+	readTimeout, err := getHTTPReadTimeout()
+	if err != nil {
+		return nil, err
+	}
 
-	httpReadTimeout, ok := os.LookupEnv("PATRON_HTTP_READ_TIMEOUT")
-	if ok {
-		readTimeout, err := time.ParseDuration(httpReadTimeout)
-		if err != nil {
-			return nil, fmt.Errorf("env var for HTTP read timeout is not valid: %w", err)
-		}
-		b.WithReadTimeout(readTimeout)
-		log.Debugf("setting up default HTTP read timeout %s", httpReadTimeout)
+	writeTimeout, err := getHTTPWriteTimeout()
+	if err != nil {
+		return nil, err
 	}
 
-	httpWriteTimeout, ok := os.LookupEnv("PATRON_HTTP_WRITE_TIMEOUT")
-	if ok {
-		writeTimeout, err := time.ParseDuration(httpWriteTimeout)
-		if err != nil {
-			return nil, fmt.Errorf("env var for HTTP write timeout is not valid: %w", err)
-		}
-		b.WithWriteTimeout(writeTimeout)
-		log.Debugf("setting up default HTTP write timeout %s", httpWriteTimeout)
+	deflateLevel, err := getHTTPDeflateLevel()
+	if err != nil {
+		return nil, err
+	}
+
+	if s.httpRouter == nil {
+		return s.createHTTPv1(int(portVal), readTimeout, writeTimeout, deflateLevel)
 	}
 
+	return s.createHTTPv2(int(portVal), readTimeout, writeTimeout)
+}
+
+func getHTTPReadTimeout() (*time.Duration, error) {
+	httpTimeout, ok := os.LookupEnv("PATRON_HTTP_READ_TIMEOUT")
+	if !ok {
+		return nil, nil
+	}
+	timeout, err := time.ParseDuration(httpTimeout)
+	if err != nil {
+		return nil, fmt.Errorf("env var for HTTP read timeout is not valid: %w", err)
+	}
+	return &timeout, nil
+}
+
+func getHTTPWriteTimeout() (*time.Duration, error) {
+	httpTimeout, ok := os.LookupEnv("PATRON_HTTP_WRITE_TIMEOUT")
+	if !ok {
+		return nil, nil
+	}
+	timeout, err := time.ParseDuration(httpTimeout)
+	if err != nil {
+		return nil, fmt.Errorf("env var for HTTP write timeout is not valid: %w", err)
+	}
+	return &timeout, nil
+}
+
+func getHTTPDeflateLevel() (*int, error) {
 	deflateLevel, ok := os.LookupEnv("PATRON_COMPRESSION_DEFLATE_LEVEL")
-	if ok {
-		deflateLevelInt, err := strconv.Atoi(deflateLevel)
-		if err != nil {
-			return nil, fmt.Errorf("env var for HTTP deflate level is not valid: %w", err)
-		}
-		b.WithDeflateLevel(deflateLevelInt)
-		log.Debugf("setting up default HTTP deflate level  %s", deflateLevel)
+	if !ok {
+		return nil, nil
+	}
+	deflateLevelInt, err := strconv.Atoi(deflateLevel)
+	if err != nil {
+		return nil, fmt.Errorf("env var for HTTP deflate level is not valid: %w", err)
+	}
+	return &deflateLevelInt, nil
+}
+
+func (s *service) createHTTPv1(port int, readTimeout, writeTimeout *time.Duration, deflateLevel *int) (Component, error) {
+	b := patronhttp.NewBuilder().WithPort(port)
+
+	if readTimeout != nil {
+		b.WithReadTimeout(*readTimeout)
+	}
+
+	if writeTimeout != nil {
+		b.WithWriteTimeout(*writeTimeout)
+	}
+
+	if deflateLevel != nil {
+		b.WithDeflateLevel(*deflateLevel)
 	}
 
 	if s.acf != nil {
@@ -155,6 +199,20 @@ func (s *service) createHTTPComponent() (Component, error) {
 	return cp, nil
 }
 
+func (s *service) createHTTPv2(port int, readTimeout, writeTimeout *time.Duration) (Component, error) {
+	oo := []v2.OptionFunc{v2.Port(port)}
+
+	if readTimeout != nil {
+		oo = append(oo, v2.ReadTimeout(*readTimeout))
+	}
+
+	if writeTimeout != nil {
+		oo = append(oo, v2.WriteTimeout(*writeTimeout))
+	}
+
+	return v2.New(s.httpRouter, oo...)
+}
+
 func (s *service) waitTermination(chErr <-chan error) error {
 	for {
 		select {
@@ -184,13 +242,14 @@ type Builder struct {
 	name              string
 	version           string
 	cps               []Component
-	routesBuilder     *http.RoutesBuilder
-	middlewares       []http.MiddlewareFunc
-	acf               http.AliveCheckFunc
-	rcf               http.ReadyCheckFunc
+	routesBuilder     *patronhttp.RoutesBuilder
+	middlewares       []middleware.Func
+	acf               patronhttp.AliveCheckFunc
+	rcf               patronhttp.ReadyCheckFunc
 	termSig           chan os.Signal
 	sighupHandler     func()
 	uncompressedPaths []string
+	httpRouter        http.Handler
 }
 
 // Config for setting up the builder.
@@ -257,8 +316,8 @@ func New(name, version string, options ...Option) (*Builder, error) {
 		errors:        make([]error, 0),
 		name:          name,
 		version:       version,
-		acf:           http.DefaultAliveCheck,
-		rcf:           http.DefaultReadyCheck,
+		acf:           patronhttp.DefaultAliveCheck,
+		rcf:           patronhttp.DefaultReadyCheck,
 		termSig:       make(chan os.Signal, 1),
 		sighupHandler: func() { log.Debug("SIGHUP received: nothing setup") },
 	}, nil
@@ -332,7 +391,10 @@ func setupJaegerTracing(name, version string) error {
 }
 
 // WithRoutesBuilder adds routes builder to the default HTTP component.
-func (b *Builder) WithRoutesBuilder(rb *http.RoutesBuilder) *Builder {
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
+func (b *Builder) WithRoutesBuilder(rb *patronhttp.RoutesBuilder) *Builder {
 	if rb == nil {
 		b.errors = append(b.errors, errors.New("routes builder is nil"))
 	} else {
@@ -344,7 +406,10 @@ func (b *Builder) WithRoutesBuilder(rb *http.RoutesBuilder) *Builder {
 }
 
 // WithMiddlewares adds generic middlewares to the default HTTP component.
-func (b *Builder) WithMiddlewares(mm ...http.MiddlewareFunc) *Builder {
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
+func (b *Builder) WithMiddlewares(mm ...middleware.Func) *Builder {
 	if len(mm) == 0 {
 		b.errors = append(b.errors, errors.New("provided middlewares slice was empty"))
 	} else {
@@ -356,7 +421,10 @@ func (b *Builder) WithMiddlewares(mm ...http.MiddlewareFunc) *Builder {
 }
 
 // WithAliveCheck overrides the default liveness check of the default HTTP component.
-func (b *Builder) WithAliveCheck(acf http.AliveCheckFunc) *Builder {
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
+func (b *Builder) WithAliveCheck(acf patronhttp.AliveCheckFunc) *Builder {
 	if acf == nil {
 		b.errors = append(b.errors, errors.New("alive check func provided was nil"))
 	} else {
@@ -368,7 +436,10 @@ func (b *Builder) WithAliveCheck(acf http.AliveCheckFunc) *Builder {
 }
 
 // WithReadyCheck overrides the default readiness check of the default HTTP component.
-func (b *Builder) WithReadyCheck(rcf http.ReadyCheckFunc) *Builder {
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
+func (b *Builder) WithReadyCheck(rcf patronhttp.ReadyCheckFunc) *Builder {
 	if rcf == nil {
 		b.errors = append(b.errors, errors.New("ready check func provided was nil"))
 	} else {
@@ -391,7 +462,7 @@ func (b *Builder) WithComponents(cc ...Component) *Builder {
 	return b
 }
 
-// WithSIGHUP adds a custom handler for when the service receives a SIGHUP.
+// WithSIGHUP adds a custom handler for handling SIGHUP.
 func (b *Builder) WithSIGHUP(handler func()) *Builder {
 	if handler == nil {
 		b.errors = append(b.errors, errors.New("provided SIGHUP handler was nil"))
@@ -403,7 +474,10 @@ func (b *Builder) WithSIGHUP(handler func()) *Builder {
 	return b
 }
 
-// WithUncompressedPaths defines a list of paths which the compression middleware will skip.
+// WithUncompressedPaths defines a list of paths which the compre ssion middleware will skip.
+//
+// Deprecated: Please use the new v2 package.
+// This package is frozen and no new functionality will be added.
 func (b *Builder) WithUncompressedPaths(p ...string) *Builder {
 	if len(p) == 0 {
 		b.errors = append(b.errors, errors.New("provided uncompressed paths slice was empty"))
@@ -415,6 +489,18 @@ func (b *Builder) WithUncompressedPaths(p ...string) *Builder {
 	return b
 }
 
+// WithRouter replaces the default v1 HTTP component with a new component v2 based on http.Handler.
+func (b *Builder) WithRouter(handler http.Handler) *Builder {
+	if handler == nil {
+		b.errors = append(b.errors, errors.New("provided router is nil"))
+	} else {
+		log.Debug("router will be used with the v2 HTTP component")
+		b.httpRouter = handler
+	}
+
+	return b
+}
+
 // Build constructs the Patron service by applying the gathered properties.
 func (b *Builder) build() (*service, error) {
 	if len(b.errors) > 0 {
@@ -436,6 +522,7 @@ func (b *Builder) build() (*service, error) {
 		termSig:           b.termSig,
 		sighupHandler:     b.sighupHandler,
 		uncompressedPaths: b.uncompressedPaths,
+		httpRouter:        b.httpRouter,
 	}
 
 	httpCp, err := s.createHTTPComponent()
@@ -449,7 +536,7 @@ func (b *Builder) build() (*service, error) {
 }
 
 // Run starts up all service components and monitors for errors.
-// If a component returns a error the service is responsible for shutting down
+// If a component returns an error the service is responsible for shutting down
 // all components and terminate itself.
 func (b *Builder) Run(ctx context.Context) error {
 	s, err := b.build()
diff --git a/trace/metric.go b/trace/metric.go
index 71254b092..3a30b4460 100644
--- a/trace/metric.go
+++ b/trace/metric.go
@@ -57,7 +57,7 @@ type Histogram struct {
 // Observe adds an observation. If there is a span associated with a context ctx the method replaces
 // the currently saved exemplar (if any) with a new one, created from the provided value.
 // NB: to have a histogram metric augmented with exemplars a histogram metric name MUST have a suffix "_bucket".
-// otherwise the metric will not be collected by Prometheus, refer to an OpenMetrics specification:
+// otherwise, the metric will not be collected by Prometheus, refer to an OpenMetrics specification:
 // https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md
 func (h *Histogram) Observe(ctx context.Context, v float64) {
 	spanFromCtx := opentracing.SpanFromContext(ctx)
diff --git a/trace/trace.go b/trace/trace.go
index b7134860e..622b4e324 100644
--- a/trace/trace.go
+++ b/trace/trace.go
@@ -90,7 +90,7 @@ func ConsumerSpan(ctx context.Context, opName, cmp, corID string, hdr map[string
 	return sp, opentracing.ContextWithSpan(ctx, sp)
 }
 
-// SpanComplete finishes a span with or without a error indicator.
+// SpanComplete finishes a span with or without an error indicator.
 func SpanComplete(sp opentracing.Span, err error) {
 	ext.Error.Set(sp, err != nil)
 	sp.Finish()
@@ -102,7 +102,7 @@ func SpanSuccess(sp opentracing.Span) {
 	sp.Finish()
 }
 
-// SpanError finishes a span with a error indicator.
+// SpanError finishes a span with an error indicator.
 func SpanError(sp opentracing.Span) {
 	ext.Error.Set(sp, true)
 	sp.Finish()
@@ -130,7 +130,7 @@ func (r consumerOption) Apply(o *opentracing.StartSpanOptions) {
 	ext.SpanKindConsumer.Apply(o)
 }
 
-// ComponentOpName returns a operation name for a component.
+// ComponentOpName returns an operation name for a component.
 func ComponentOpName(cmp, target string) string {
 	return cmp + " " + target
 }


Test patch:
diff --git a/component/async/component_test.go b/component/async/component_test.go
index 764416021..a760c9a6f 100644
--- a/component/async/component_test.go
+++ b/component/async/component_test.go
@@ -412,7 +412,7 @@ func TestRun_Process_Shutdown(t *testing.T) {
 	assert.Equal(t, 1, builder.proc.execs)
 }
 
-// TestRun_Process_Error_InvalidStrategy expects a invalid failure strategy error
+// TestRun_Process_Error_InvalidStrategy expects an invalid failure strategy error
 // NOTE : we injected the failure strategy after the construction,
 // in order to avoid the failure strategy check.
 func TestRun_Process_Error_InvalidStrategy(t *testing.T) {
diff --git a/component/http/component_test.go b/component/http/component_test.go
index e0e77cdf9..b8dadfa93 100644
--- a/component/http/component_test.go
+++ b/component/http/component_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/beatlabs/patron/component/http/middleware"
 	errs "github.com/beatlabs/patron/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -124,7 +125,7 @@ func Test_createHTTPServerUsingBuilder(t *testing.T) {
 		gp       time.Duration
 		dl       int
 		rb       *RoutesBuilder
-		mm       []MiddlewareFunc
+		mm       []middleware.Func
 		c        string
 		k        string
 		wantErrs []error
@@ -138,9 +139,8 @@ func Test_createHTTPServerUsingBuilder(t *testing.T) {
 			dl:  deflateLevel,
 			gp:  shutdownGracePeriod,
 			rb:  rb,
-			mm: []MiddlewareFunc{
-				NewRecoveryMiddleware(),
-				panicMiddleware("error"),
+			mm: []middleware.Func{
+				middleware.NewRecovery(),
 			},
 			c:        "cert.file",
 			k:        "key.file",
@@ -155,7 +155,7 @@ func Test_createHTTPServerUsingBuilder(t *testing.T) {
 			gp:       -15 * time.Second,
 			dl:       -8,
 			rb:       nil,
-			mm:       []MiddlewareFunc{},
+			mm:       []middleware.Func{},
 			c:        "",
 			k:        "",
 			wantErrs: httpBuilderAllErrors,
diff --git a/component/http/handler_test.go b/component/http/handler_test.go
index 79829f4ce..a82db346e 100644
--- a/component/http/handler_test.go
+++ b/component/http/handler_test.go
@@ -8,7 +8,6 @@ import (
 	"net/http/httptest"
 	"testing"
 
-	"github.com/beatlabs/patron/correlation"
 	"github.com/beatlabs/patron/encoding"
 	"github.com/beatlabs/patron/encoding/json"
 	"github.com/beatlabs/patron/encoding/protobuf"
@@ -122,33 +121,6 @@ func request(t *testing.T, contentType, accept string) *http.Request {
 	return req
 }
 
-func Test_getOrSetCorrelationID(t *testing.T) {
-	t.Parallel()
-	withID := http.Header{correlation.HeaderID: []string{"123"}}
-	withoutID := http.Header{correlation.HeaderID: []string{}}
-	withEmptyID := http.Header{correlation.HeaderID: []string{""}}
-	missingHeader := http.Header{}
-	type args struct {
-		hdr http.Header
-	}
-	tests := map[string]struct {
-		args args
-	}{
-		"with id":        {args: args{hdr: withID}},
-		"without id":     {args: args{hdr: withoutID}},
-		"with empty id":  {args: args{hdr: withEmptyID}},
-		"missing Header": {args: args{hdr: missingHeader}},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			assert.NotEmpty(t, getOrSetCorrelationID(tt.args.hdr))
-			assert.NotEmpty(t, tt.args.hdr[correlation.HeaderID][0])
-		})
-	}
-}
-
 func Test_handleSuccess(t *testing.T) {
 	t.Parallel()
 	get, err := http.NewRequest(http.MethodGet, "/", nil)
diff --git a/component/http/logging_test.go b/component/http/middleware/logging_test.go
similarity index 95%
rename from component/http/logging_test.go
rename to component/http/middleware/logging_test.go
index 64a2b6d24..8384f92f8 100644
--- a/component/http/logging_test.go
+++ b/component/http/middleware/logging_test.go
@@ -1,4 +1,4 @@
-package http
+package middleware
 
 import (
 	"testing"
@@ -43,7 +43,7 @@ func TestStatusCode(t *testing.T) {
 		tt := tt
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
-			h, err := newStatusCodeLoggerHandler(tt.args.cfg)
+			h, err := NewStatusCodeLoggerHandler(tt.args.cfg)
 			if tt.expectedParsingErr {
 				assert.Error(t, err)
 			} else {
@@ -55,7 +55,7 @@ func TestStatusCode(t *testing.T) {
 }
 
 func BenchmarkName(b *testing.B) {
-	h, err := newStatusCodeLoggerHandler(complexConfig)
+	h, err := NewStatusCodeLoggerHandler(complexConfig)
 	assert.NoError(b, err)
 	b.ReportAllocs()
 	b.ResetTimer()
diff --git a/component/http/middleware_test.go b/component/http/middleware/middleware_test.go
similarity index 80%
rename from component/http/middleware_test.go
rename to component/http/middleware/middleware_test.go
index c4d82f140..9a8049a18 100644
--- a/component/http/middleware_test.go
+++ b/component/http/middleware/middleware_test.go
@@ -1,4 +1,4 @@
-package http
+package middleware
 
 import (
 	"errors"
@@ -7,16 +7,28 @@ import (
 	"net/http/httptest"
 	"testing"
 
-	"golang.org/x/time/rate"
-
+	httpcache "github.com/beatlabs/patron/component/http/cache"
 	"github.com/opentracing/opentracing-go"
 	"github.com/opentracing/opentracing-go/mocktracer"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"golang.org/x/time/rate"
 )
 
+type stubAuthenticator struct {
+	success bool
+	err     error
+}
+
+func (mo stubAuthenticator) Authenticate(_ *http.Request) (bool, error) {
+	if mo.err != nil {
+		return false, mo.err
+	}
+	return mo.success, nil
+}
+
 // A middleware generator that tags resp for assertions.
-func tagMiddleware(tag string) MiddlewareFunc {
+func tagMiddleware(tag string) Func {
 	return func(h http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			_, _ = w.Write([]byte(tag))
@@ -27,7 +39,7 @@ func tagMiddleware(tag string) MiddlewareFunc {
 }
 
 // Panic middleware to test recovery middleware.
-func panicMiddleware(v interface{}) MiddlewareFunc {
+func panicMiddleware(v interface{}) Func {
 	return func(h http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			panic(v)
@@ -56,7 +68,7 @@ func TestMiddlewareChain(t *testing.T) {
 
 	type args struct {
 		next http.Handler
-		mws  []MiddlewareFunc
+		mws  []Func
 	}
 	tests := []struct {
 		name         string
@@ -64,15 +76,15 @@ func TestMiddlewareChain(t *testing.T) {
 		expectedCode int
 		expectedBody string
 	}{
-		{"middleware 1,2,3 and finish", args{next: handler, mws: []MiddlewareFunc{t1, t2, t3}}, 202, "t1\nt2\nt3\n"},
-		{"middleware 1,2 and finish", args{next: handler, mws: []MiddlewareFunc{t1, t2}}, 202, "t1\nt2\n"},
-		{"no middleware and finish", args{next: handler, mws: []MiddlewareFunc{}}, 202, ""},
+		{"middleware 1,2,3 and finish", args{next: handler, mws: []Func{t1, t2, t3}}, 202, "t1\nt2\nt3\n"},
+		{"middleware 1,2 and finish", args{next: handler, mws: []Func{t1, t2}}, 202, "t1\nt2\n"},
+		{"no middleware and finish", args{next: handler, mws: []Func{}}, 202, ""},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			rc := httptest.NewRecorder()
 			rw := newResponseWriter(rc, true)
-			tt.args.next = MiddlewareChain(tt.args.next, tt.args.mws...)
+			tt.args.next = Chain(tt.args.next, tt.args.mws...)
 			tt.args.next.ServeHTTP(rw, r)
 			assert.Equal(t, tt.expectedCode, rw.Status())
 			assert.Equal(t, tt.expectedBody, rc.Body.String())
@@ -90,7 +102,7 @@ func TestMiddlewares(t *testing.T) {
 
 	type args struct {
 		next http.Handler
-		mws  []MiddlewareFunc
+		mws  []Func
 	}
 	tests := []struct {
 		name         string
@@ -98,21 +110,21 @@ func TestMiddlewares(t *testing.T) {
 		expectedCode int
 		expectedBody string
 	}{
-		{"auth middleware success", args{next: handler, mws: []MiddlewareFunc{NewAuthMiddleware(&MockAuthenticator{success: true})}}, 202, ""},
-		{"auth middleware false", args{next: handler, mws: []MiddlewareFunc{NewAuthMiddleware(&MockAuthenticator{success: false})}}, 401, "Unauthorized\n"},
-		{"auth middleware error", args{next: handler, mws: []MiddlewareFunc{NewAuthMiddleware(&MockAuthenticator{err: errors.New("auth error")})}}, 500, "Internal Server Error\n"},
-		{"tracing middleware", args{next: handler, mws: []MiddlewareFunc{NewLoggingTracingMiddleware("/index", statusCodeLoggerHandler{})}}, 202, ""},
-		{"rate limiting middleware", args{next: handler, mws: []MiddlewareFunc{NewRateLimitingMiddleware(getMockLimiter(true))}}, 202, ""},
-		{"rate limiting middleware error", args{next: handler, mws: []MiddlewareFunc{NewRateLimitingMiddleware(getMockLimiter(false))}}, 429, "Requests greater than limit\n"},
-		{"recovery middleware from panic 1", args{next: handler, mws: []MiddlewareFunc{NewRecoveryMiddleware(), panicMiddleware("error")}}, 500, "Internal Server Error\n"},
-		{"recovery middleware from panic 2", args{next: handler, mws: []MiddlewareFunc{NewRecoveryMiddleware(), panicMiddleware(errors.New("error"))}}, 500, "Internal Server Error\n"},
-		{"recovery middleware from panic 3", args{next: handler, mws: []MiddlewareFunc{NewRecoveryMiddleware(), panicMiddleware(-1)}}, 500, "Internal Server Error\n"},
+		{"auth middleware success", args{next: handler, mws: []Func{NewAuth(&stubAuthenticator{success: true})}}, 202, ""},
+		{"auth middleware false", args{next: handler, mws: []Func{NewAuth(&stubAuthenticator{success: false})}}, 401, "Unauthorized\n"},
+		{"auth middleware error", args{next: handler, mws: []Func{NewAuth(&stubAuthenticator{err: errors.New("auth error")})}}, 500, "Internal Server Error\n"},
+		{"tracing middleware", args{next: handler, mws: []Func{NewLoggingTracing("/index", StatusCodeLoggerHandler{})}}, 202, ""},
+		{"rate limiting middleware", args{next: handler, mws: []Func{NewRateLimiting(getMockLimiter(true))}}, 202, ""},
+		{"rate limiting middleware error", args{next: handler, mws: []Func{NewRateLimiting(getMockLimiter(false))}}, 429, "Requests greater than limit\n"},
+		{"recovery middleware from panic 1", args{next: handler, mws: []Func{NewRecovery(), panicMiddleware("error")}}, 500, "Internal Server Error\n"},
+		{"recovery middleware from panic 2", args{next: handler, mws: []Func{NewRecovery(), panicMiddleware(errors.New("error"))}}, 500, "Internal Server Error\n"},
+		{"recovery middleware from panic 3", args{next: handler, mws: []Func{NewRecovery(), panicMiddleware(-1)}}, 500, "Internal Server Error\n"},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			rc := httptest.NewRecorder()
 			rw := newResponseWriter(rc, true)
-			tt.args.next = MiddlewareChain(tt.args.next, tt.args.mws...)
+			tt.args.next = Chain(tt.args.next, tt.args.mws...)
 			tt.args.next.ServeHTTP(rw, r)
 			assert.Equal(t, tt.expectedCode, rw.Status())
 			assert.Equal(t, tt.expectedBody, rc.Body.String())
@@ -139,7 +151,7 @@ func TestSpanLogError(t *testing.T) {
 
 	type args struct {
 		next http.Handler
-		mws  []MiddlewareFunc
+		mws  []Func
 	}
 	tests := []struct {
 		name                 string
@@ -148,15 +160,15 @@ func TestSpanLogError(t *testing.T) {
 		expectedBody         string
 		expectedSpanLogError string
 	}{
-		{"tracing middleware - error", args{next: errorHandler, mws: []MiddlewareFunc{NewLoggingTracingMiddleware("/index", statusCodeLoggerHandler{})}}, http.StatusInternalServerError, "foo", "foo"},
-		{"tracing middleware - success", args{next: successHandler, mws: []MiddlewareFunc{NewLoggingTracingMiddleware("/index", statusCodeLoggerHandler{})}}, http.StatusOK, "", ""},
+		{"tracing middleware - error", args{next: errorHandler, mws: []Func{NewLoggingTracing("/index", StatusCodeLoggerHandler{})}}, http.StatusInternalServerError, "foo", "foo"},
+		{"tracing middleware - success", args{next: successHandler, mws: []Func{NewLoggingTracing("/index", StatusCodeLoggerHandler{})}}, http.StatusOK, "", ""},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			mtr.Reset()
 			rc := httptest.NewRecorder()
 			rw := newResponseWriter(rc, true)
-			tt.args.next = MiddlewareChain(tt.args.next, tt.args.mws...)
+			tt.args.next = Chain(tt.args.next, tt.args.mws...)
 			tt.args.next.ServeHTTP(rw, r)
 			assert.Equal(t, tt.expectedCode, rw.Status())
 			assert.Equal(t, tt.expectedBody, rc.Body.String())
@@ -261,13 +273,14 @@ func getSpanLogError(t *testing.T, span *mocktracer.MockSpan) string {
 
 func TestNewCompressionMiddleware(t *testing.T) {
 	tests := map[string]struct {
-		cm MiddlewareFunc
+		cm Func
 	}{
-		"gzip":    {cm: NewCompressionMiddleware(8)},
-		"deflate": {cm: NewCompressionMiddleware(8)},
+		"gzip":    {cm: NewCompression(8)},
+		"deflate": {cm: NewCompression(8)},
 	}
 
-	for name, tc := range tests {
+	for name, tt := range tests {
+		tt := tt
 		t.Run(name, func(t *testing.T) {
 			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Add("Content-Length", "123")
@@ -277,7 +290,7 @@ func TestNewCompressionMiddleware(t *testing.T) {
 			assert.NoError(t, err)
 
 			req.Header.Set("Accept-Encoding", name)
-			compressionMiddleware := tc.cm
+			compressionMiddleware := tt.cm
 			assert.NoError(t, err)
 			assert.NotNil(t, compressionMiddleware)
 
@@ -294,7 +307,7 @@ func TestNewCompressionMiddleware(t *testing.T) {
 
 func TestNewCompressionMiddlewareServer(t *testing.T) {
 	tests := []struct {
-		cm               MiddlewareFunc
+		cm               Func
 		status           int
 		acceptEncoding   string
 		expectedEncoding string
@@ -303,82 +316,83 @@ func TestNewCompressionMiddlewareServer(t *testing.T) {
 			status:           200,
 			acceptEncoding:   "gzip",
 			expectedEncoding: "gzip",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           201,
 			acceptEncoding:   "gzip",
 			expectedEncoding: "gzip",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           204,
 			acceptEncoding:   "gzip",
 			expectedEncoding: "",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           304,
 			acceptEncoding:   "gzip",
 			expectedEncoding: "",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           404,
 			acceptEncoding:   "gzip",
 			expectedEncoding: "gzip",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           200,
 			acceptEncoding:   "deflate",
 			expectedEncoding: "deflate",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           201,
 			acceptEncoding:   "deflate",
 			expectedEncoding: "deflate",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           204,
 			acceptEncoding:   "deflate",
 			expectedEncoding: "",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           304,
 			acceptEncoding:   "deflate",
 			expectedEncoding: "",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 		{
 			status:           404,
 			acceptEncoding:   "deflate",
 			expectedEncoding: "deflate",
-			cm:               NewCompressionMiddleware(8),
+			cm:               NewCompression(8),
 		},
 	}
 
-	for _, tc := range tests {
-		t.Run(fmt.Sprintf("%d - %s", tc.status, tc.expectedEncoding), func(t *testing.T) {
+	for _, tt := range tests {
+		tt := tt
+		t.Run(fmt.Sprintf("%d - %s", tt.status, tt.expectedEncoding), func(t *testing.T) {
 			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-				w.WriteHeader(tc.status)
+				w.WriteHeader(tt.status)
 			})
 
-			compressionMiddleware := tc.cm
+			compressionMiddleware := tt.cm
 			assert.NotNil(t, compressionMiddleware)
 			s := httptest.NewServer(compressionMiddleware(handler))
 			defer s.Close()
 
 			req, err := http.NewRequest("GET", s.URL, nil)
 			assert.NoError(t, err)
-			req.Header.Set("Accept-Encoding", tc.acceptEncoding)
+			req.Header.Set("Accept-Encoding", tt.acceptEncoding)
 
 			resp, err := s.Client().Do(req)
 			assert.Nil(t, err)
-			assert.Equal(t, tc.expectedEncoding, resp.Header.Get("Content-Encoding"))
+			assert.Equal(t, tt.expectedEncoding, resp.Header.Get("Content-Encoding"))
 		})
 	}
 }
@@ -387,7 +401,7 @@ func TestNewCompressionMiddleware_Ignore(t *testing.T) {
 	var ceh string // accept-encoding, content type
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(202) })
-	middleware := NewCompressionMiddleware(8, "/metrics")
+	middleware := NewCompression(8, "/metrics")
 
 	assert.NotNil(t, middleware)
 
@@ -419,10 +433,10 @@ func TestNewCompressionMiddleware_Ignore(t *testing.T) {
 
 func TestNewCompressionMiddleware_Headers(t *testing.T) {
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
-	middleware := NewCompressionMiddleware(8, "/metrics")
+	middleware := NewCompression(8, "/metrics")
 
 	tests := map[string]struct {
-		cm               MiddlewareFunc
+		cm               Func
 		statusCode       int
 		encodingExpected string
 	}{
@@ -711,18 +725,18 @@ func (fw *failWriter) WriteHeader(_ int) {
 
 func TestSetResponseWriterStatusOnResponseFailWrite(t *testing.T) {
 	failWriter := &failWriter{}
-	failDynamicCompressionResponseWriter := &dynamicCompressionResponseWriter{failWriter, "", nil, 0, deflateLevel}
+	failDynamicCompressionResponseWriter := &dynamicCompressionResponseWriter{failWriter, "", nil, 0, 6}
 
 	tests := []struct {
 		Name           string
 		ResponseWriter *responseWriter
 	}{
 		{
-			Name:           "Failing responseWriter with http.ResponseWriter",
+			Name:           "Failing responseWriter with http.responseWriter",
 			ResponseWriter: newResponseWriter(failWriter, false),
 		},
 		{
-			Name:           "Failing responseWriter with http.ResponseWriter",
+			Name:           "Failing responseWriter with http.responseWriter",
 			ResponseWriter: newResponseWriter(failDynamicCompressionResponseWriter, false),
 		},
 	}
@@ -735,3 +749,48 @@ func TestSetResponseWriterStatusOnResponseFailWrite(t *testing.T) {
 		})
 	}
 }
+
+func TestNewInjectObservability(t *testing.T) {
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })
+	middleware := NewInjectObservability()
+	assert.NotNil(t, middleware)
+
+	// check if the route actually ignored
+	req, err := http.NewRequest("GET", "/metrics", nil)
+	assert.NoError(t, err)
+
+	rc := httptest.NewRecorder()
+	middleware(handler).ServeHTTP(rc, req)
+
+	assert.Equal(t, 200, rc.Code)
+}
+
+func TestNewCaching(t *testing.T) {
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })
+	middleware := NewCaching(&httpcache.RouteCache{})
+	assert.NotNil(t, middleware)
+
+	// check if the route actually ignored
+	req, err := http.NewRequest("GET", "/metrics", nil)
+	assert.NoError(t, err)
+
+	rc := httptest.NewRecorder()
+	middleware(handler).ServeHTTP(rc, req)
+
+	assert.Equal(t, 200, rc.Code)
+}
+
+func TestNewRequestObserver(t *testing.T) {
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })
+	middleware := NewRequestObserver(http.MethodGet, "/api")
+	assert.NotNil(t, middleware)
+
+	// check if the route actually ignored
+	req, err := http.NewRequest("GET", "/api", nil)
+	assert.NoError(t, err)
+
+	rc := httptest.NewRecorder()
+	middleware(handler).ServeHTTP(rc, req)
+
+	assert.Equal(t, 200, rc.Code)
+}
diff --git a/component/http/route_cache_test.go b/component/http/route_cache_test.go
index 9ee60f56b..988488cff 100644
--- a/component/http/route_cache_test.go
+++ b/component/http/route_cache_test.go
@@ -10,13 +10,12 @@ import (
 	"testing"
 	"time"
 
-	"github.com/stretchr/testify/require"
-
-	"github.com/beatlabs/patron/encoding"
-
 	"github.com/beatlabs/patron/cache"
 	httpclient "github.com/beatlabs/patron/client/http"
 	httpcache "github.com/beatlabs/patron/component/http/cache"
+	"github.com/beatlabs/patron/component/http/middleware"
+	"github.com/beatlabs/patron/encoding"
+	"github.com/stretchr/testify/require"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -44,7 +43,7 @@ func TestCachingMiddleware(t *testing.T) {
 
 	type args struct {
 		next http.Handler
-		mws  []MiddlewareFunc
+		mws  []middleware.Func
 	}
 
 	testingCache := newTestingCache()
@@ -68,7 +67,7 @@ func TestCachingMiddleware(t *testing.T) {
 				i, err := w.Write([]byte{1, 2, 3, 4})
 				assert.NoError(t, err)
 				assert.Equal(t, 4, i)
-			}), mws: []MiddlewareFunc{NewCachingMiddleware(routeCache)}},
+			}), mws: []middleware.Func{middleware.NewCaching(routeCache)}},
 			postRequest, 202, "\x01\x02\x03\x04",
 			cacheState{},
 		},
@@ -79,7 +78,7 @@ func TestCachingMiddleware(t *testing.T) {
 				i, err := w.Write([]byte{1, 2, 3, 4})
 				assert.NoError(t, err)
 				assert.Equal(t, 4, i)
-			}), mws: []MiddlewareFunc{NewCachingMiddleware(routeCache)}},
+			}), mws: []middleware.Func{middleware.NewCaching(routeCache)}},
 			getRequest, 200, "\x01\x02\x03\x04",
 			cacheState{
 				setOps: 1,
@@ -91,8 +90,8 @@ func TestCachingMiddleware(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			rc := httptest.NewRecorder()
-			rw := newResponseWriter(rc, true)
-			tt.args.next = MiddlewareChain(tt.args.next, tt.args.mws...)
+			rw := &stubResponseWriter{writer: rc}
+			tt.args.next = middleware.Chain(tt.args.next, tt.args.mws...)
 			tt.args.next.ServeHTTP(rw, tt.r)
 			assert.Equal(t, tt.expectedCode, rw.Status())
 			assert.Equal(t, tt.expectedBody, rc.Body.String())
@@ -256,7 +255,7 @@ func TestRouteCacheAsMiddleware_WithSingleRequest(t *testing.T) {
 	}).
 		WithMiddlewares(
 			preWrapper.middleware,
-			NewCachingMiddleware(routeCache),
+			middleware.NewCaching(routeCache),
 			postWrapper.middleware).
 		MethodGet()
 
@@ -307,7 +306,7 @@ func TestRouteCacheAsMiddleware_WithSingleRequest(t *testing.T) {
 }
 
 type middlewareWrapper struct {
-	middleware  MiddlewareFunc
+	middleware  middleware.Func
 	invocations int
 }
 
@@ -555,3 +554,37 @@ func (t *testingCache) SetTTL(key string, value interface{}, ttl time.Duration)
 func (t *testingCache) size() int {
 	return len(t.cache)
 }
+
+type stubResponseWriter struct {
+	status              int
+	statusHeaderWritten bool
+	writer              http.ResponseWriter
+}
+
+func (w *stubResponseWriter) Status() int {
+	return w.status
+}
+
+func (w *stubResponseWriter) Header() http.Header {
+	return w.writer.Header()
+}
+
+func (w *stubResponseWriter) Write(d []byte) (int, error) {
+	if !w.statusHeaderWritten {
+		w.status = http.StatusOK
+		w.statusHeaderWritten = true
+	}
+
+	value, err := w.writer.Write(d)
+	if err != nil {
+		return value, err
+	}
+
+	return value, err
+}
+
+func (w *stubResponseWriter) WriteHeader(code int) {
+	w.status = code
+	w.writer.WriteHeader(code)
+	w.statusHeaderWritten = true
+}
diff --git a/component/http/route_test.go b/component/http/route_test.go
index 4f38cb991..8a4f960e1 100644
--- a/component/http/route_test.go
+++ b/component/http/route_test.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/beatlabs/patron/component/http/auth"
 	"github.com/beatlabs/patron/component/http/cache"
+	"github.com/beatlabs/patron/component/http/middleware"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -106,16 +107,16 @@ func TestRouteBuilder_WithTrace(t *testing.T) {
 
 func TestRouteBuilder_WithMiddlewares(t *testing.T) {
 	t.Parallel()
-	middleware := func(next http.Handler) http.Handler { return next }
+	mw := func(next http.Handler) http.Handler { return next }
 	mockHandler := func(http.ResponseWriter, *http.Request) {}
 	type fields struct {
-		middlewares []MiddlewareFunc
+		middlewares []middleware.Func
 	}
 	tests := map[string]struct {
 		fields      fields
 		expectedErr string
 	}{
-		"success":            {fields: fields{middlewares: []MiddlewareFunc{middleware}}},
+		"success":            {fields: fields{middlewares: []middleware.Func{mw}}},
 		"missing middleware": {fields: fields{}, expectedErr: "middlewares are empty"},
 	}
 	for name, tt := range tests {
@@ -190,7 +191,7 @@ func TestRouteBuilder_Build(t *testing.T) {
 	t.Parallel()
 	mockAuth := &MockAuthenticator{}
 	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	middleware := func(next http.Handler) http.Handler { return next }
+	mw := func(next http.Handler) http.Handler { return next }
 	type fields struct {
 		path          string
 		missingMethod bool
@@ -207,7 +208,7 @@ func TestRouteBuilder_Build(t *testing.T) {
 		tt := tt
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
-			rb := NewRouteBuilder(tt.fields.path, mockProcessor).WithTrace().WithAuth(mockAuth).WithMiddlewares(middleware).WithRateLimiting(5, 50)
+			rb := NewRouteBuilder(tt.fields.path, mockProcessor).WithTrace().WithAuth(mockAuth).WithMiddlewares(mw).WithRateLimiting(5, 50)
 			if !tt.fields.missingMethod {
 				rb.MethodGet()
 			}
diff --git a/component/http/v2/check_test.go b/component/http/v2/check_test.go
new file mode 100644
index 000000000..4c32fda01
--- /dev/null
+++ b/component/http/v2/check_test.go
@@ -0,0 +1,70 @@
+package v2
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_aliveCheckRoute(t *testing.T) {
+	t.Parallel()
+	tests := map[string]struct {
+		acf  LivenessCheckFunc
+		want int
+	}{
+		"alive":        {func() AliveStatus { return Alive }, http.StatusOK},
+		"unresponsive": {func() AliveStatus { return Unhealthy }, http.StatusServiceUnavailable},
+		"default":      {func() AliveStatus { return 10 }, http.StatusServiceUnavailable},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			route, err := LivenessCheckRoute(tt.acf)
+			assert.NoError(t, err)
+			assert.Equal(t, http.MethodGet, route.method)
+			assert.Equal(t, "/alive", route.path)
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest(http.MethodGet, "/alive", nil)
+			require.NoError(t, err)
+
+			route.handler(resp, req)
+
+			assert.Equal(t, tt.want, resp.Code)
+		})
+	}
+}
+
+func Test_readyCheckRoute(t *testing.T) {
+	t.Parallel()
+	tests := map[string]struct {
+		rcf  ReadyCheckFunc
+		want int
+	}{
+		"ready":    {func() ReadyStatus { return Ready }, http.StatusOK},
+		"notReady": {func() ReadyStatus { return NotReady }, http.StatusServiceUnavailable},
+		"default":  {func() ReadyStatus { return 10 }, http.StatusServiceUnavailable},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			route, err := ReadyCheckRoute(tt.rcf)
+			assert.NoError(t, err)
+			assert.Equal(t, http.MethodGet, route.method)
+			assert.Equal(t, "/ready", route.path)
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest(http.MethodGet, "/ready", nil)
+			require.NoError(t, err)
+
+			route.handler(resp, req)
+
+			assert.Equal(t, tt.want, resp.Code)
+		})
+	}
+}
diff --git a/component/http/v2/component_option_test.go b/component/http/v2/component_option_test.go
new file mode 100644
index 000000000..8c5fc147b
--- /dev/null
+++ b/component/http/v2/component_option_test.go
@@ -0,0 +1,185 @@
+package v2
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTLS(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		cert string
+		key  string
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":      {args: args{cert: "cert", key: "key"}},
+		"missing cert": {args: args{cert: "", key: "key"}, expectedErr: "cert file or key file was empty"},
+		"missing key":  {args: args{cert: "cert", key: ""}, expectedErr: "cert file or key file was empty"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cmp := &Component{}
+			err := TLS(tt.args.cert, tt.args.key)(cmp)
+
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.args.cert, cmp.certFile)
+				assert.Equal(t, tt.args.key, cmp.keyFile)
+			}
+		})
+	}
+}
+
+func TestReadTimeout(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		rt time.Duration
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":      {args: args{rt: time.Second}},
+		"missing cert": {args: args{rt: -1 * time.Second}, expectedErr: "negative or zero read timeout provided"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cmp := &Component{}
+			err := ReadTimeout(tt.args.rt)(cmp)
+
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.args.rt, cmp.readTimeout)
+			}
+		})
+	}
+}
+
+func TestWriteTimeout(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		wt time.Duration
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":      {args: args{wt: time.Second}},
+		"missing cert": {args: args{wt: -1 * time.Second}, expectedErr: "negative or zero write timeout provided"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cmp := &Component{}
+			err := WriteTimeout(tt.args.wt)(cmp)
+
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.args.wt, cmp.writeTimeout)
+			}
+		})
+	}
+}
+
+func TestHandlerTimeout(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		wt time.Duration
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":      {args: args{wt: time.Second}},
+		"missing cert": {args: args{wt: -1 * time.Second}, expectedErr: "negative or zero handler timeout provided"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cmp := &Component{}
+			err := HandlerTimeout(tt.args.wt)(cmp)
+
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.args.wt, cmp.handlerTimeout)
+			}
+		})
+	}
+}
+
+func TestShutdownGracePeriod(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		gp time.Duration
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":      {args: args{gp: time.Second}},
+		"missing cert": {args: args{gp: -1 * time.Second}, expectedErr: "negative or zero shutdown grace period timeout provided"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cmp := &Component{}
+			err := ShutdownGracePeriod(tt.args.gp)(cmp)
+
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.args.gp, cmp.shutdownGracePeriod)
+			}
+		})
+	}
+}
+
+func TestPort(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		port int
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":      {args: args{port: 50000}},
+		"missing cert": {args: args{port: 120000}, expectedErr: "invalid HTTP Port provided"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cmp := &Component{}
+			err := Port(tt.args.port)(cmp)
+
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.args.port, cmp.port)
+			}
+		})
+	}
+}
diff --git a/component/http/v2/component_test.go b/component/http/v2/component_test.go
new file mode 100644
index 000000000..45fe92fcf
--- /dev/null
+++ b/component/http/v2/component_test.go
@@ -0,0 +1,81 @@
+package v2
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+type stubHandler struct{}
+
+func (s stubHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) {
+	rw.WriteHeader(200)
+}
+
+func TestNew(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		handler http.Handler
+		oo      []OptionFunc
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{
+			handler: &stubHandler{},
+			oo:      []OptionFunc{Port(50000)},
+		}},
+		"missing handler": {args: args{
+			handler: nil,
+			oo:      []OptionFunc{Port(50000)},
+		}, expectedErr: "handler is nil"},
+		"option error": {args: args{
+			handler: &stubHandler{},
+			oo:      []OptionFunc{Port(500000)},
+		}, expectedErr: "invalid HTTP Port provided"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			got, err := New(tt.args.handler, tt.args.oo...)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+				assert.Nil(t, got)
+			} else {
+				assert.NoError(t, err)
+				assert.NotNil(t, got)
+			}
+		})
+	}
+}
+
+func TestComponent_ListenAndServe_DefaultRoutes_Shutdown(t *testing.T) {
+	listener, err := net.Listen("tcp", ":0") //nolint:gosec
+	require.NoError(t, err)
+	port, ok := listener.Addr().(*net.TCPAddr)
+	assert.True(t, ok)
+	require.NoError(t, listener.Close())
+
+	cmp, err := New(&stubHandler{}, Port(port.Port))
+	assert.NoError(t, err)
+	done := make(chan bool)
+	ctx, cnl := context.WithCancel(context.Background())
+	go func() {
+		assert.NoError(t, cmp.Run(ctx))
+		done <- true
+	}()
+	time.Sleep(10 * time.Millisecond)
+	rsp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port.Port))
+	assert.NoError(t, err)
+	assert.Equal(t, http.StatusOK, rsp.StatusCode)
+	cnl()
+	assert.True(t, <-done)
+}
diff --git a/component/http/v2/observability_test.go b/component/http/v2/observability_test.go
new file mode 100644
index 000000000..502586faa
--- /dev/null
+++ b/component/http/v2/observability_test.go
@@ -0,0 +1,91 @@
+package v2
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_metricRoute(t *testing.T) {
+	route := MetricRoute()
+	assert.Equal(t, http.MethodGet, route.method)
+	assert.Equal(t, "/metrics", route.path)
+
+	resp := httptest.NewRecorder()
+	req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
+	require.NoError(t, err)
+
+	route.handler(resp, req)
+
+	assert.Equal(t, http.StatusOK, resp.Code)
+}
+
+type profilingTestCase struct {
+	path string
+	want int
+}
+
+func TestProfilingRoutes(t *testing.T) {
+	t.Run("without vars", func(t *testing.T) {
+		server := createProfilingServer(false)
+		defer server.Close()
+
+		for name, tt := range createProfilingTestCases(false) {
+			tt := tt
+			t.Run(name, func(t *testing.T) {
+				resp, err := http.Get(fmt.Sprintf("%s/%s", server.URL, tt.path))
+				assert.NoError(t, err)
+				assert.Equal(t, tt.want, resp.StatusCode)
+			})
+		}
+	})
+
+	t.Run("with vars", func(t *testing.T) {
+		server := createProfilingServer(true)
+		defer server.Close()
+
+		for name, tt := range createProfilingTestCases(true) {
+			tt := tt
+			t.Run(name, func(t *testing.T) {
+				resp, err := http.Get(fmt.Sprintf("%s/%s", server.URL, tt.path))
+				assert.NoError(t, err)
+				assert.Equal(t, tt.want, resp.StatusCode)
+			})
+		}
+	})
+}
+
+func createProfilingServer(enableExpVar bool) *httptest.Server {
+	mux := http.NewServeMux()
+	for _, route := range ProfilingRoutes(enableExpVar) {
+		mux.HandleFunc(route.path, route.handler)
+	}
+
+	return httptest.NewServer(mux)
+}
+
+func createProfilingTestCases(enableExpVar bool) map[string]profilingTestCase {
+	expVarWant := 404
+	if enableExpVar {
+		expVarWant = 200
+	}
+
+	return map[string]profilingTestCase{
+		"index":        {"/debug/pprof/", 200},
+		"allocs":       {"/debug/pprof/allocs/", 200},
+		"cmdline":      {"/debug/pprof/cmdline/", 200},
+		"profile":      {"/debug/pprof/profile/?seconds=1", 200},
+		"symbol":       {"/debug/pprof/symbol/", 200},
+		"trace":        {"/debug/pprof/trace/?seconds=1", 200},
+		"heap":         {"/debug/pprof/heap/", 200},
+		"goroutine":    {"/debug/pprof/goroutine/", 200},
+		"block":        {"/debug/pprof/block/", 200},
+		"threadcreate": {"/debug/pprof/threadcreate/", 200},
+		"mutex":        {"/debug/pprof/mutex/", 200},
+		"vars":         {"/debug/vars/", expVarWant},
+	}
+}
diff --git a/component/http/v2/route_option_test.go b/component/http/v2/route_option_test.go
new file mode 100644
index 000000000..95877bb1b
--- /dev/null
+++ b/component/http/v2/route_option_test.go
@@ -0,0 +1,130 @@
+package v2
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/beatlabs/patron/cache"
+	"github.com/beatlabs/patron/cache/redis"
+	"github.com/beatlabs/patron/component/http/auth"
+	httpcache "github.com/beatlabs/patron/component/http/cache"
+	patronhttp "github.com/beatlabs/patron/component/http/middleware"
+	"github.com/stretchr/testify/assert"
+)
+
+type mockAuthenticator struct {
+	success bool
+	err     error
+}
+
+func (mo mockAuthenticator) Authenticate(_ *http.Request) (bool, error) {
+	if mo.err != nil {
+		return false, mo.err
+	}
+	return mo.success, nil
+}
+
+func TestRateLimiting(t *testing.T) {
+	t.Parallel()
+	route := &Route{}
+	assert.NoError(t, RateLimiting(0, 0)(route))
+}
+
+func TestRouteMiddlewares(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		mm []patronhttp.Func
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{mm: []patronhttp.Func{patronhttp.NewRecovery()}}},
+		"fail":    {args: args{mm: nil}, expectedErr: "middlewares are empty"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			route := &Route{}
+			err := Middlewares(tt.args.mm...)(route)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.Len(t, route.middlewares, 1)
+			}
+		})
+	}
+}
+
+func TestAuth(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		auth auth.Authenticator
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{auth: &mockAuthenticator{}}},
+		"fail":    {args: args{auth: nil}, expectedErr: "authenticator is nil"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			route := &Route{}
+			err := Auth(tt.args.auth)(route)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.Len(t, route.middlewares, 1)
+			}
+		})
+	}
+}
+
+func TestCache(t *testing.T) {
+	t.Parallel()
+	type fields struct {
+		httpMethod string
+	}
+	type args struct {
+		cache     cache.TTLCache
+		ageBounds httpcache.Age
+	}
+	tests := map[string]struct {
+		fields      fields
+		args        args
+		expectedErr string
+	}{
+		"success": {
+			fields:      fields{httpMethod: http.MethodGet},
+			args:        args{cache: &redis.Cache{}, ageBounds: httpcache.Age{}},
+			expectedErr: "",
+		},
+		"fail with missing get": {
+			fields:      fields{httpMethod: http.MethodDelete},
+			args:        args{cache: &redis.Cache{}, ageBounds: httpcache.Age{}},
+			expectedErr: "cannot apply cache to a route with any method other than GET",
+		},
+		"fail with args": {
+			fields:      fields{httpMethod: http.MethodGet},
+			args:        args{cache: nil, ageBounds: httpcache.Age{}},
+			expectedErr: "route cache is nil\n",
+		},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			route := &Route{method: tt.fields.httpMethod}
+			err := Cache(tt.args.cache, tt.args.ageBounds)(route)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.Len(t, route.middlewares, 1)
+			}
+		})
+	}
+}
diff --git a/component/http/v2/route_test.go b/component/http/v2/route_test.go
new file mode 100644
index 000000000..d5763a9ef
--- /dev/null
+++ b/component/http/v2/route_test.go
@@ -0,0 +1,175 @@
+package v2
+
+import (
+	"errors"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewRoute(t *testing.T) {
+	t.Parallel()
+
+	handler := func(http.ResponseWriter, *http.Request) {}
+
+	type args struct {
+		method      string
+		path        string
+		handler     http.HandlerFunc
+		optionFuncs []RouteOptionFunc
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{
+			method:      http.MethodGet,
+			path:        "/api",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{RateLimiting(1, 1)},
+		}},
+		"missing method": {args: args{
+			method:      "",
+			path:        "/api",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{RateLimiting(1, 1)},
+		}, expectedErr: "method is empty"},
+		"missing path": {args: args{
+			method:      http.MethodGet,
+			path:        "",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{RateLimiting(1, 1)},
+		}, expectedErr: "path is empty"},
+		"missing handler": {args: args{
+			method:      http.MethodGet,
+			path:        "/api",
+			handler:     nil,
+			optionFuncs: []RouteOptionFunc{RateLimiting(1, 1)},
+		}, expectedErr: "handler is nil"},
+		"missing middlewares": {args: args{
+			method:      http.MethodGet,
+			path:        "/api",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{Middlewares()},
+		}, expectedErr: "middlewares are empty"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			got, err := NewRoute(tt.args.method, tt.args.path, tt.args.handler, tt.args.optionFuncs...)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+				assert.Nil(t, got)
+			} else {
+				assert.NoError(t, err)
+				assertRoute(t, tt.args.method, tt.args.path, got)
+				assert.Equal(t, "GET /api", got.String())
+			}
+		})
+	}
+}
+
+func TestNewGetRoute(t *testing.T) {
+	route, err := NewGetRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodGet, "/api", route)
+}
+
+func TestNewHeadRoute(t *testing.T) {
+	route, err := NewHeadRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodHead, "/api", route)
+}
+
+func TestNewPostRoute(t *testing.T) {
+	route, err := NewPostRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodPost, "/api", route)
+}
+
+func TestNewPutRoute(t *testing.T) {
+	route, err := NewPutRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodPut, "/api", route)
+}
+
+func TestNewPatchRoute(t *testing.T) {
+	route, err := NewPatchRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodPatch, "/api", route)
+}
+
+func TestNewDeleteRoute(t *testing.T) {
+	route, err := NewDeleteRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodDelete, "/api", route)
+}
+
+func TestNewConnectRoute(t *testing.T) {
+	route, err := NewConnectRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodConnect, "/api", route)
+}
+
+func TestNewOptionsRoute(t *testing.T) {
+	route, err := NewOptionsRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodOptions, "/api", route)
+}
+
+func TestNewTraceRoute(t *testing.T) {
+	route, err := NewTraceRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{RateLimiting(1, 1)}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodTrace, "/api", route)
+}
+
+func assertRoute(t *testing.T, method, path string, route *Route) {
+	assert.Equal(t, method, route.Method())
+	assert.Equal(t, path, route.Path())
+	assert.NotNil(t, route.Handler())
+	assert.Len(t, route.Middlewares(), 1)
+}
+
+func TestRoutes_Append(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		route *Route
+		err   error
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":      {args: args{route: &Route{}, err: nil}},
+		"error exist":  {args: args{route: &Route{}, err: errors.New("TEST")}, expectedErr: "TEST\n"},
+		"route is nil": {args: args{route: nil, err: nil}, expectedErr: "route is nil\n"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			r := &Routes{}
+			r.Append(tt.args.route, tt.args.err)
+			routes, err := r.Result()
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+				assert.Empty(t, routes)
+			} else {
+				assert.NoError(t, err)
+				assert.NotEmpty(t, routes)
+			}
+		})
+	}
+}
diff --git a/component/http/v2/router/httprouter/route_test.go b/component/http/v2/router/httprouter/route_test.go
new file mode 100644
index 000000000..24b7056d5
--- /dev/null
+++ b/component/http/v2/router/httprouter/route_test.go
@@ -0,0 +1,87 @@
+package httprouter
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewFileServerRoute(t *testing.T) {
+	type args struct {
+		path         string
+		assetsDir    string
+		fallbackPath string
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{
+			path:         "/frontend/*path",
+			assetsDir:    "testdata/",
+			fallbackPath: "testdata/index.html",
+		}},
+		"missing path": {args: args{
+			path:         "",
+			assetsDir:    "123",
+			fallbackPath: "123",
+		}, expectedErr: "path is empty"},
+		"missing assets": {args: args{
+			path:         "123",
+			assetsDir:    "",
+			fallbackPath: "123",
+		}, expectedErr: "assets path is empty"},
+		"missing fallback path": {args: args{
+			path:         "123",
+			assetsDir:    "123",
+			fallbackPath: "",
+		}, expectedErr: "fallback path is empty"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			got, err := NewFileServerRoute(tt.args.path, tt.args.assetsDir, tt.args.fallbackPath)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+				assert.Nil(t, got)
+			} else {
+				assert.NoError(t, err)
+				assert.NotNil(t, got)
+				assert.Equal(t, "/frontend/*path", got.Path())
+				assert.Equal(t, http.MethodGet, got.Method())
+				assert.NotNil(t, got.Handler())
+				assert.Len(t, got.Middlewares(), 0)
+			}
+		})
+	}
+}
+
+func TestFileServerRouteHandler(t *testing.T) {
+	handler, err := NewFileServerRoute("/frontend/*path", "testdata/", "testdata/index.html")
+	require.NoError(t, err)
+
+	type args struct {
+		path string
+	}
+	tests := map[string]struct {
+		args         args
+		expectedCode int
+		expectedErr  string
+	}{
+		"fallback": {args: args{path: "frontend"}, expectedCode: 200},
+		"index":    {args: args{path: "frontend/index"}, expectedCode: 200},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			req, err := http.NewRequest(http.MethodGet, tt.args.path, nil)
+			require.NoError(t, err)
+			rc := httptest.NewRecorder()
+			handler.Handler().ServeHTTP(rc, req)
+			assert.Equal(t, tt.expectedCode, rc.Code)
+		})
+	}
+}
diff --git a/component/http/v2/router/httprouter/router_test.go b/component/http/v2/router/httprouter/router_test.go
new file mode 100644
index 000000000..551f08934
--- /dev/null
+++ b/component/http/v2/router/httprouter/router_test.go
@@ -0,0 +1,165 @@
+package httprouter
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/beatlabs/patron/component/http/middleware"
+	"github.com/beatlabs/patron/component/http/v2"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNew(t *testing.T) {
+	t.Parallel()
+	route, err := v2.NewRoute(http.MethodGet, "/api/", func(writer http.ResponseWriter, request *http.Request) {
+		writer.WriteHeader(200)
+	})
+	require.NoError(t, err)
+	type args struct {
+		oo []OptionFunc
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success":            {args: args{oo: []OptionFunc{Routes(route)}}},
+		"option func failed": {args: args{oo: []OptionFunc{AliveCheck(nil)}}, expectedErr: "alive check function is nil"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			got, err := New(tt.args.oo...)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NotNil(t, got)
+			}
+		})
+	}
+}
+
+func TestRoutes(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		routes []*v2.Route
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{routes: []*v2.Route{{}, {}}}},
+		"fail":    {args: args{routes: nil}, expectedErr: "routes are empty"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cfg := &Config{}
+			err := Routes(tt.args.routes...)(cfg)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.Equal(t, tt.args.routes, cfg.routes)
+			}
+		})
+	}
+}
+
+func TestAliveCheck(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		acf v2.LivenessCheckFunc
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{acf: func() v2.AliveStatus { return v2.Alive }}},
+		"fail":    {args: args{acf: nil}, expectedErr: "alive check function is nil"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cfg := &Config{}
+			err := AliveCheck(tt.args.acf)(cfg)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NotNil(t, cfg.aliveCheckFunc)
+			}
+		})
+	}
+}
+
+func TestReadyCheck(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		rcf v2.ReadyCheckFunc
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{rcf: func() v2.ReadyStatus { return v2.Ready }}},
+		"fail":    {args: args{rcf: nil}, expectedErr: "ready check function is nil"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cfg := &Config{}
+			err := ReadyCheck(tt.args.rcf)(cfg)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.NotNil(t, cfg.readyCheckFunc)
+			}
+		})
+	}
+}
+
+func TestDeflateLevel(t *testing.T) {
+	t.Parallel()
+	cfg := &Config{}
+	err := DeflateLevel(10)(cfg)
+	assert.NoError(t, err)
+	assert.Equal(t, 10, cfg.deflateLevel)
+}
+
+func TestMiddlewares(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		mm []middleware.Func
+	}
+	tests := map[string]struct {
+		args        args
+		expectedErr string
+	}{
+		"success": {args: args{mm: []middleware.Func{func(next http.Handler) http.Handler { return next }}}},
+		"fail":    {args: args{mm: nil}, expectedErr: "middlewares are empty"},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			cfg := &Config{}
+			err := Middlewares(tt.args.mm...)(cfg)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+			} else {
+				assert.Len(t, cfg.middlewares, 1)
+			}
+		})
+	}
+}
+
+func TestDisableProfiling(t *testing.T) {
+	t.Parallel()
+	cfg := &Config{}
+	err := EnableExpVarProfiling()(cfg)
+	assert.NoError(t, err)
+	assert.True(t, cfg.enableProfilingExpVar)
+}
diff --git a/component/http/v2/router/httprouter/testdata/index.html b/component/http/v2/router/httprouter/testdata/index.html
new file mode 100644
index 000000000..c4f61846e
--- /dev/null
+++ b/component/http/v2/router/httprouter/testdata/index.html
@@ -0,0 +1,6 @@
+<html lang="en">
+    <head><title>Patron</title></head>
+    <body>
+        Served by Patron
+    </body>
+</html>
\ No newline at end of file
diff --git a/correlation/correlation_test.go b/correlation/correlation_test.go
index 1c7a729f2..1f1f8261a 100644
--- a/correlation/correlation_test.go
+++ b/correlation/correlation_test.go
@@ -2,6 +2,7 @@ package correlation
 
 import (
 	"context"
+	"net/http"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -35,3 +36,30 @@ func TestContextWithID(t *testing.T) {
 	assert.True(t, ok)
 	assert.Equal(t, "123", val)
 }
+
+func TestGetOrSetHeaderID(t *testing.T) {
+	t.Parallel()
+	withID := http.Header{HeaderID: []string{"123"}}
+	withoutID := http.Header{HeaderID: []string{}}
+	withEmptyID := http.Header{HeaderID: []string{""}}
+	missingHeader := http.Header{}
+	type args struct {
+		hdr http.Header
+	}
+	tests := map[string]struct {
+		args args
+	}{
+		"with id":        {args: args{hdr: withID}},
+		"without id":     {args: args{hdr: withoutID}},
+		"with empty id":  {args: args{hdr: withEmptyID}},
+		"missing Header": {args: args{hdr: missingHeader}},
+	}
+	for name, tt := range tests {
+		tt := tt
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			assert.NotEmpty(t, GetOrSetHeaderID(tt.args.hdr))
+			assert.NotEmpty(t, tt.args.hdr[HeaderID][0])
+		})
+	}
+}
diff --git a/log/std/logger_test.go b/log/std/logger_test.go
index 3c31edf72..40ec2c8dc 100644
--- a/log/std/logger_test.go
+++ b/log/std/logger_test.go
@@ -58,6 +58,8 @@ func TestNewSub(t *testing.T) {
 	assert.Equal(t, subLogger.fields, map[string]interface{}{"name": "john doe", "age": 18})
 	assert.Contains(t, subLogger.fieldsLine, "age=18")
 	assert.Contains(t, subLogger.fieldsLine, "name=john doe")
+	subLogger.Info()
+	assert.Contains(t, b.String(), "age=18")
 }
 
 func TestLogger(t *testing.T) {
diff --git a/reliability/retry/retry_test.go b/reliability/retry/retry_test.go
index b84d2ef5e..e28e1077e 100644
--- a/reliability/retry/retry_test.go
+++ b/reliability/retry/retry_test.go
@@ -99,7 +99,7 @@ func Test_Retry_Execute(t *testing.T) {
 
 			assert.Equal(t, tC.expectedExecutions, tC.action.executions)
 
-			// Assert that the total time taken takes into account the delay between retries
+			// Assert that the total time takes into account the delay between retries
 			assert.True(t, elapsed > tC.delay*time.Duration(tC.expectedExecutions-1))
 		})
 	}
diff --git a/service_test.go b/service_test.go
index 71d726649..f3a48c635 100644
--- a/service_test.go
+++ b/service_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 
 	patronhttp "github.com/beatlabs/patron/component/http"
+	"github.com/beatlabs/patron/component/http/middleware"
 	"github.com/beatlabs/patron/log"
 	"github.com/beatlabs/patron/log/std"
 	"github.com/stretchr/testify/assert"
@@ -22,7 +23,7 @@ func TestNewServer(t *testing.T) {
 	routesBuilder := patronhttp.NewRoutesBuilder().
 		Append(patronhttp.NewRawRouteBuilder("/", func(w http.ResponseWriter, r *http.Request) {}).MethodGet())
 
-	middleware := func(h http.Handler) http.Handler {
+	mw := func(h http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			h.ServeHTTP(w, r)
 		})
@@ -34,28 +35,31 @@ func TestNewServer(t *testing.T) {
 		"ready check func provided was nil\n" +
 		"provided components slice was empty\n" +
 		"provided SIGHUP handler was nil\n" +
-		"provided uncompressed paths slice was empty\n"
+		"provided uncompressed paths slice was empty\n" +
+		"provided router is nil\n"
 
 	tests := map[string]struct {
 		fields            map[string]interface{}
 		cps               []Component
 		routesBuilder     *patronhttp.RoutesBuilder
-		middlewares       []patronhttp.MiddlewareFunc
+		middlewares       []middleware.Func
 		acf               patronhttp.AliveCheckFunc
 		rcf               patronhttp.ReadyCheckFunc
 		sighupHandler     func()
 		uncompressedPaths []string
+		handler           http.Handler
 		wantErr           string
 	}{
 		"success": {
 			fields:            map[string]interface{}{"env": "dev"},
 			cps:               []Component{&testComponent{}, &testComponent{}},
 			routesBuilder:     routesBuilder,
-			middlewares:       []patronhttp.MiddlewareFunc{middleware},
+			middlewares:       []middleware.Func{mw},
 			acf:               patronhttp.DefaultAliveCheck,
 			rcf:               patronhttp.DefaultReadyCheck,
 			sighupHandler:     func() { log.Info("SIGHUP received: nothing setup") },
 			uncompressedPaths: []string{"/foo", "/bar"},
+			handler:           mw(nil),
 			wantErr:           "",
 		},
 		"nil inputs steps": {
@@ -66,16 +70,18 @@ func TestNewServer(t *testing.T) {
 			rcf:               nil,
 			sighupHandler:     nil,
 			uncompressedPaths: nil,
+			handler:           nil,
 			wantErr:           httpBuilderAllErrors,
 		},
 		"error in all builder steps": {
 			cps:               []Component{},
 			routesBuilder:     nil,
-			middlewares:       []patronhttp.MiddlewareFunc{},
+			middlewares:       []middleware.Func{},
 			acf:               nil,
 			rcf:               nil,
 			sighupHandler:     nil,
 			uncompressedPaths: []string{},
+			handler:           nil,
 			wantErr:           httpBuilderAllErrors,
 		},
 	}
@@ -94,6 +100,7 @@ func TestNewServer(t *testing.T) {
 				WithComponents(tt.cps...).
 				WithSIGHUP(tt.sighupHandler).
 				WithUncompressedPaths(tt.uncompressedPaths...).
+				WithRouter(tt.handler).
 				build()
 
 			if tt.wantErr != "" {
@@ -116,8 +123,8 @@ func TestNewServer(t *testing.T) {
 					assert.Contains(t, gotService.cps, comp)
 				}
 
-				for _, middleware := range tt.middlewares {
-					assert.NotNil(t, middleware)
+				for _, mw := range tt.middlewares {
+					assert.NotNil(t, mw)
 				}
 			}
 		})