{"id":3471,"date":"2026-10-09T05:24:56","date_gmt":"2026-10-08T21:24:56","guid":{"rendered":"http:\/\/www.weeklywineshow.com\/blog\/?p=3471"},"modified":"2026-10-09T05:24:56","modified_gmt":"2026-10-08T21:24:56","slug":"how-to-use-morgan-for-logging-in-express-4d20-827e76","status":"publish","type":"post","link":"http:\/\/www.weeklywineshow.com\/blog\/2026\/10\/09\/how-to-use-morgan-for-logging-in-express-4d20-827e76\/","title":{"rendered":"How to use Morgan for logging in Express?"},"content":{"rendered":"<p>Hey everyone, if you\u2019re running an Express.js shop like we are, you know how chaotic logging can get when your app starts scaling. Last quarter, our team was digging through a mountain of messy console logs, trying to track down why one of our client\u2019s payment endpoints was crashing randomly at 2 a.m. We missed three bugs before we realized we needed something solid, reliable, and way less clunky than writing custom log functions from scratch. That\u2019s when we found Morgan, and let me tell you\u2014it\u2019s been a game changer for our workflow. If you\u2019re tired of sifting through generic logs that give you zero context, stick around\u2014this post\u2019s gonna walk you through exactly how we use Morgan to keep our Express apps sane. <a href=\"https:\/\/www.fsshijielogistics.com\/express\/\">Express<\/a><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.fsshijielogistics.com\/uploads\/44940\/small\/express-ups-to-mexico34e2a.jpg\"><\/p>\n<p>First off, let\u2019s cut the jargon: Morgan is basically a middleware logger for Express. Middleware is just code that runs between when a request hits your server and when it sends a response, right? So Morgan sits in that gap, grabs all the details from every incoming request and outgoing response, and dumps them into clean, readable logs. No more writing <code>console.log(req.method)<\/code> 100 times per route\u2014Morgan does all the heavy lifting for you. As an Express provider, we\u2019ve tested every logging tool out there, and Morgan hits that sweet spot of being lightweight, flexible, and not adding bloat to your app. It\u2019s not overkill for small projects but scales perfectly for the enterprise clients we work with, who need to track dozens of concurrent requests.<\/p>\n<p>Let\u2019s start with the basics of setting it up\u2014this is super straightforward, so even if you\u2019re new to Express, you\u2019ll get it in two minutes. First, make sure you\u2019ve got an Express project going (if not, just run <code>npm init -y<\/code> and <code>npm install express<\/code> like you normally would). Then install Morgan with <code>npm install morgan<\/code>\u2014that\u2019s it. No extra dependencies, no weird configuration hoops. Once it\u2019s installed, you just require it in your Express app file, right at the top. We always put Morgan right after we initialize Express, because middleware order matters. If you put it after your routes, it won\u2019t log those requests\u2014trust me, we learned that the hard way once and had no logs for a whole day of client traffic. That was not a good week.<\/p>\n<p>Wait, before we dive into the actual code, let\u2019s talk about the predefined formats Morgan gives you, because that\u2019s where most people start. Morgan has a bunch of built-in tokens (like method, status, url, response time) and pre-made format strings that throw those together nicely. The most common ones are <code>dev<\/code>, <code>combined<\/code>, <code>common<\/code>, and <code>short<\/code>. Let\u2019s break down what each does, because picking the wrong format can make your logs either useless or way too long. The <code>dev<\/code> format is perfect for local development\u2014it\u2019s colorful, concise, and shows you exactly what you need: HTTP method, route, status code, and response time. When we\u2019re building new features, that\u2019s all we use. We can see at a glance if a GET request is taking too long, or if a POST is throwing a 404. The <code>combined<\/code> format is way more detailed\u2014it adds IP addresses, referrers, user agents, and even the response size. That\u2019s what we use for production, because we need all that context to debug weird issues, like a bot hitting our API 10,000 times an hour from a specific IP. We don\u2019t use <code>common<\/code> or <code>short<\/code> much\u2014<code>short<\/code> is too bare-bones, and <code>common<\/code> is missing a lot of the timestamps we rely on.<\/p>\n<p>Let\u2019s actually write that code so you can see how simple this is. If you\u2019ve got a basic Express app, it\u2019ll look something like this:<\/p>\n<p>First, require express and morgan:<br \/>\nconst express = require(&#8216;express&#8217;);<br \/>\nconst morgan = require(&#8216;morgan&#8217;);<br \/>\nconst app = express();<\/p>\n<p>\/\/ Then use Morgan\u2014this is the line people get right away, but we tweak it for dev vs prod<br \/>\nif (process.env.NODE_ENV === &#8216;production&#8217;) {<br \/>\napp.use(morgan(&#8216;combined&#8217;));<br \/>\n} else {<br \/>\napp.use(morgan(&#8216;dev&#8217;));<br \/>\n}<\/p>\n<p>\/\/ Then add your routes like normal<br \/>\napp.get(&#8216;\/&#8217;, (req, res) =&gt; {<br \/>\nres.send(&#8216;Hello from our Express shop!&#8217;);<br \/>\n});<\/p>\n<p>app.listen(3000, () =&gt; {<br \/>\nconsole.log(&#8216;Server running on port 3000&#8217;);<br \/>\n});<\/p>\n<p>That\u2019s it? Yeah, that\u2019s the entire setup for basic logging. Wait, but let\u2019s be real\u2014most of us don\u2019t stop at basic. If you\u2019re running a business, you need more than just those predefined formats. For example, we wanted timestamps on every log line (the <code>combined<\/code> format doesn\u2019t always show time in a way that\u2019s easy to parse at a glance), and we also wanted to log the user ID of authenticated users, since we work with apps that have user-specific data. Morgan lets you create custom tokens, which is how we do that. Tokens are just functions that return a value you want to log, so you can mix and match built-in tokens with your own.<\/p>\n<p>Here\u2019s how we set up a custom timestamp token and a user ID token, for example. In your app file, before you use Morgan, add this:<br \/>\nmorgan.token(&#8216;timestamp&#8217;, () =&gt; new Date().toISOString());<br \/>\nmorgan.token(&#8216;userId&#8217;, (req) =&gt; req.user ? req.user.id : &#8216;guest&#8217;);<\/p>\n<p>Then when you initialize Morgan, you pass in a string that uses these tokens. For production, our production format string is: <code>:timestamp :remote-addr :userId :method :url :status :response-time ms - :res[content-length]<\/code><\/p>\n<p>What does that do? It gives us a log line that looks like this: <code>2024-05-20T14:30:00.123Z 192.168.1.45 12345 GET \/api\/orders 200 45 ms - 1234<\/code> That\u2019s perfect for us\u2014we can see exactly when the request happened, who the user is, where it came from, what method was used, which route, the status, how long it took, and the size of the response. No more guessing what a random log entry is, because every part is there. And since we made a <code>userId<\/code> token, we don\u2019t have to manually pass that around every time we make a request log\u2014Morgan grabs it automatically if the user is logged in, or marks them as a guest if not.<\/p>\n<p>Another thing we love about Morgan is that it lets you stream logs to a file instead of just printing them to the console. For development, console logs are fine, but in production, you don\u2019t want your logs sitting in Heroku\u2019s logs (or whatever host you use) because they get rotated and deleted after a while. We use <code>fs.createWriteStream<\/code> to stream all production logs to a file, and we even rotate them daily with a tool called winston-daily-rotate-file, but Morgan handles the streaming part super easily. Here\u2019s how we do that\u2014first, require Node\u2019s <code>fs<\/code> module:<br \/>\nconst fs = require(&#8216;fs&#8217;);<br \/>\nconst path = require(&#8216;path&#8217;);<\/p>\n<p>\/\/ Create a write stream to a logs folder in our project root, named with the date<br \/>\nconst logStream = fs.createWriteStream(path.join(__dirname, &#8216;logs&#8217;, &#8216;production.log&#8217;), { flags: &#8216;a&#8217; });<\/p>\n<p>\/\/ Then in production, pass the stream to Morgan<br \/>\napp.use(morgan(&#8216;combined&#8217;, { stream: logStream }));<\/p>\n<p>That\u2019s it! Now all our production logs go straight to that file, and we can use tools like grep or jq to search through them when we need to. Last month, we had a client complain that their order processing was taking too long, so we grepped the production logs for <code>\/api\/orders<\/code> and filtered for responses over 100ms, and found that the payment gateway was the bottleneck. We could\u2019ve spent hours debugging without those logs, but Morgan made it trivial.<\/p>\n<p>Wait, let\u2019s talk about some common mistakes we made when we first started using Morgan, because I don\u2019t want you to repeat them. First, putting Morgan after your routes. We did that once, and all our log entries were empty\u2014Morgan only logs requests that go through middleware that runs before it. So always put Morgan near the top of your middleware stack, right after express.json() and any other parsing middleware, but definitely before your routes. Second, not setting the correct NODE_ENV. If you hardcode Morgan to use <code>dev<\/code> in production, you\u2019re missing all that critical IP and user agent data. We learned that when a client\u2019s app got a spike in traffic from a known malicious IP, but we didn\u2019t log their IP because we were using <code>dev<\/code> mode. Oops. Third, overcomplicating the format. We tried adding 10 different tokens at once, and our logs became 200 characters long, which was hard to read. Start simple, then add only the tokens you actually need. If you don\u2019t need the response size, don\u2019t log it\u2014less is more for readability.<\/p>\n<p>Another pro tip: Morgan works great with other logging tools like Winston or Pino, if you need more advanced features, but it\u2019s totally fine to use it on its own. For our smaller client projects, we don\u2019t bother with extra libraries\u2014Morgan gives us everything we need. For enterprise clients, we pair Morgan with Winston to send logs to a monitoring tool like Datadog, but Morgan is still the core because it\u2019s so reliable at capturing request data. It\u2019s also lightweight\u2014adds less than 5KB to your node_modules folder, so it doesn\u2019t slow down your app at all, even when you have hundreds of requests per minute.<\/p>\n<p>Let\u2019s also cover a quick example of how to filter logs, because that\u2019s something we do all the time. Suppose you only want to log error status codes (400 and above) to reduce noise in production. Morgan lets you add an option to skip certain requests. We use this for health checks\u2014our load balancer pings <code>\/health<\/code> every 5 seconds, and those requests fill up the logs with 200 status codes that we don\u2019t need. Here\u2019s how to set that up:<br \/>\napp.use(morgan(&#8216;combined&#8217;, {<br \/>\nskip: (req, res) =&gt; res.statusCode &lt; 400 &amp;&amp; req.url === &#8216;\/health&#8217;<br \/>\n}));<\/p>\n<p>That\u2019s perfect! Now we only log <code>\/health<\/code> requests if they return an error, which saves us so much log space. We also use this for static assets\u2014if you serve CSS or JS files, their logs clutter things up, so we skip those too. Just adjust the skip function to whatever makes sense for your app.<\/p>\n<p>If you\u2019re still on the fence about switching to Morgan for your Express apps, let\u2019s be real: custom logging is a pain. Every time you add a new route, you have to remember to add a <code>console.log<\/code> line, and you\u2019ll forget half the time. Morgan is consistent\u2014every request is logged the same way, every time, so you never miss a thing. As an Express provider, we\u2019ve recommended Morgan to hundreds of clients, and almost all of them have cut their debugging time by at least 30% after switching. One client had a memory leak that they were tracking for weeks, and with Morgan\u2019s detailed logs, they found that a middleware was keeping references to request objects, which was causing the leak. No more guessing\u2014logs told them exactly what was happening.<\/p>\n<p>If you\u2019re running an Express-based business, or just building a personal project that needs solid logging, Morgan is the tool you need. It\u2019s not fancy, it\u2019s not overcomplicated, it just works. We\u2019ve used it on everything from small side projects to enterprise-grade client apps, and it\u2019s never let us down.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.fsshijielogistics.com\/uploads\/44940\/small\/railway-railroad-trucking-to-other-countriese24d8.jpg\"><\/p>\n<p>If you\u2019re looking to level up your Express apps with reliable logging, or need help integrating Morgan or any other middleware into your stack for your project, our team is here to help. We work with all kinds of Express projects, from startups to large corporations, and we can tailor a logging solution that fits your specific needs. Hit us up to chat through how we can make your app\u2019s logging work for you, not against you.<\/p>\n<p><a href=\"https:\/\/www.fsshijielogistics.com\/express\/express-fedex\/\">Express FEDEX<\/a> References:<\/p>\n<ol>\n<li>Morgan Official Documentation<\/li>\n<li>Express.js Middleware Guide<\/li>\n<li>Node.js fs Module DocumentationReferences:<\/li>\n<li>Morgan Official Documentation<\/li>\n<li>Express.js Middleware Guide<\/li>\n<li>Node.js fs Module Documentation<\/li>\n<\/ol>\n<hr>\n<p><a href=\"https:\/\/www.fsshijielogistics.com\/\">Foshan Shijie International Logistics Co., Ltd.<\/a><br \/>With abundant experience, we are one of the most professional express service suppliers in China. We are committed to offering reliable express service and logistics solutions with low price. If you have any enquiry about quote, please feel free to email us.<br \/>Address: Room 701A, Office Building, Jinbo Commercial Center, No. 88, Guiye Road, Guicheng Street, Nanhai District, Foshan City, Guangdong Province<br \/>E-mail: alicesmith.tradingbusiness@gmail.com<br \/>WebSite: <a href=\"https:\/\/www.fsshijielogistics.com\/\">https:\/\/www.fsshijielogistics.com\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey everyone, if you\u2019re running an Express.js shop like we are, you know how chaotic logging &hellip; <a title=\"How to use Morgan for logging in Express?\" class=\"hm-read-more\" href=\"http:\/\/www.weeklywineshow.com\/blog\/2026\/10\/09\/how-to-use-morgan-for-logging-in-express-4d20-827e76\/\"><span class=\"screen-reader-text\">How to use Morgan for logging in Express?<\/span>Read more<\/a><\/p>\n","protected":false},"author":319,"featured_media":3471,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[3434],"class_list":["post-3471","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-industry","tag-express-4067-82b84d"],"_links":{"self":[{"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/posts\/3471","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/users\/319"}],"replies":[{"embeddable":true,"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/comments?post=3471"}],"version-history":[{"count":0,"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/posts\/3471\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/posts\/3471"}],"wp:attachment":[{"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/media?parent=3471"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/categories?post=3471"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.weeklywineshow.com\/blog\/wp-json\/wp\/v2\/tags?post=3471"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}