<rss version="2.0">
  <channel>
    <title>Welcome to the LymeStack Blog</title>
    <link>https://www.lymestack.com/blog</link>
    <description><![CDATA[Discussing Angular + .NET Core WebApi Web Development Topics]]></description>
    <item>
      <title>Implementing Auth in .NET WebApi &amp; SPAs: Why is it still so painful?</title>
      <link>https://www.lymestack.com/blog/implementing-auth-in-net-spas-why-is-it-still-so-painful</link>
      <description><![CDATA[<p>I wanted to reboot a conversation about something that has frustrated me repeatedly over the years: implementing authentication in .NET WebAPIs and SPAs (Angular, React, Vue, etc.).</p>
<p>TL; DR: In searching other Reddit posts on this topic, I found this post from a few months back: <a href="https://www.reddit.com/r/dotnet/comments/1g7b0eb/what_do_you_use_as_an_auth_provider">What do you use as an auth provider?</a></p>
<p>Microsoft Identity was the top answer, and for good reason—it’s a powerful, tried-and-true solution. But despite its strengths, I find it unnecessarily complex for many of my projects and IMHO, I find it difficult to wrap my head around. Also, it seems so half-baked for being such a mature product. There’s so much work and code to write in order to implement the framework into an app from-scratch.</p>
<p>Is anyone else here feeling my pain?</p>
<hr>
<p><strong>Implementing Auth Sucks.</strong></p>
<p>It’s unavoidable for any semi-serious app and often times it’s the very first thing you need to implement, significantly delaying any progress you can make on the core functionality of your app. Your app needs to know <em>who</em> your users are in order to deliver personalized information and functionality. It seems so App 101, but getting authentication set up has always felt much harder than its academic course-level assignment would suggest.</p>
<p>I describe the problem to lay-people: “You know that login screen you see on your bank's website or that Sign-in with Google or Facebook button you see?” Everyone, even my non-tech mom understands. “Well, as it turns out, that’s a huge PAIN IN THE ASS to build!” Even mom doesn’t believe that it seems as difficult as it is for such a common use-case.</p>
<p>My process for implementing auth usually looks something like this:</p>
<ol>
<li>Research and pick from a giant list of options: Microsoft Identity, OAuth providers, commercial identity systems, or even rolling my own solution.</li>
<li>Google for examples or tutorials that match my stack (e.g., “.NET WebAPI identity example” or “Angular WebApi OAuth tutorial”).</li>
<li>Spend hours following a tutorial to the end only to that something doesn’t work as described because of any number of reasons or a change to the tech may have happened since the tutorial was written (e.g. Startup.cs moving to Program.cs)</li>
<li>Fiddle with it for hours, scouring forums, stack overflow posts, conversing w/ GPT, to often give up and start over with a different tutorial.</li>
<li>Eventually, I get something working and hold onto it for dear life—until I need to implement auth again in a new stack or use a new provider.</li>
</ol>
<p>I can honestly say that implementing auth solutions is one of the worst parts of my job!</p>
<hr>
<p>For me, Microsoft Identity works, but it has two big downsides:</p>
<ol>
<li>It’s complex: There’s a steep learning curve, even for experienced developers.</li>
<li>It lacks a management UI out of the box or any polished front-end implementation: You’re still stuck building out UI for common workflows like registration / password resets and tools to manage users, roles, and tokens yourself.</li>
</ol>
<hr>
<p>What about commercial identity providers?</p>
<p>If you’re willing to pay for it, you can get a polished, managed solution that <em>just works</em>. But for smaller projects or businesses, the cost can be prohibitive. Why should simple auth cost so much?</p>
<hr>
<p>The case for on-premises auth solutions</p>
<p>This is something I think about a lot. Apps that can run on-premises or offer multiple SSO options are <em>more resilient</em>. They’re impervious to internet outages or any one provider going down. This becomes even more important for apps running in:</p>
<ul>
<li>Secure labs</li>
<li>Disconnected environments (off-grid)</li>
<li>Industries with strict regulatory requirements</li>
</ul>
<p>An ideal solution would give administrators the flexibility to turn on or off local or cloud providers as needed—without tying you to a single system (shout-out MSAL)!</p>
<hr>
<p>What’s your experience been like?</p>
<p>I’m curious:</p>
<ul>
<li>What’s your biggest pain point when implementing auth in .NET and SPAs?</li>
<li>How do you handle it today?</li>
<li>What would an ideal solution look like for you?</li>
</ul>
<p>Let’s talk. I’m hoping to learn from others, and maybe we can find a better way together.</p>
]]></description>
      <pubDate>Thu, 12 Dec 2024 18:28:33 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/implementing-auth-in-net-spas-why-is-it-still-so-painful</guid>
    </item>
    <item>
      <title>Implementing Auth in .NET WebApi &amp; SPAs: Why is it still so painful? (Redirect)</title>
      <link>https://www.lymestack.com/blog/welcome-to-the-lymestack-blog/implementing-auth-in-net-spas-why-is-it-still-so-painful</link>
      <description><![CDATA[<p>This post has been moved to here:
<a href="https://www.lymestack.com/blog/implementing-auth-in-net-spas-why-is-it-still-so-painful">https://www.lymestack.com/blog/implementing-auth-in-net-spas-why-is-it-still-so-painful</a></p>
]]></description>
      <pubDate>Thu, 12 Dec 2024 17:42:26 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/welcome-to-the-lymestack-blog/implementing-auth-in-net-spas-why-is-it-still-so-painful</guid>
    </item>
    <item>
      <title>Introducing SimpleAuth for .NET: A Simpler Way to Handle Authentication</title>
      <link>https://www.lymestack.com/blog/introducing-simpleauth4net</link>
      <description><![CDATA[<h2><strong>Implementing Auth Shouldn't Be This Hard</strong></h2>
<p>If you’ve been a developer for any length of time, you’ve probably faced the dreaded task of implementing <strong>user authentication and authorization</strong>. Whether you’re working with <strong>SSO providers</strong> like Google, managing <strong>local accounts</strong>, or building role-based access, the process can be a massive headache.</p>
<ul>
<li>The <strong>documentation</strong> often feels fragmented or outdated.</li>
<li>Tutorials are hit-or-miss and rarely work out of the box.</li>
<li>Frameworks like <strong>ASP.NET Core Identity</strong> are powerful but come with a steep learning curve.</li>
</ul>
<p>And at the end of the day, all you want is a clean, straightforward way to sign users in and secure your API.</p>
<p>This was my frustration, too—so I built <strong>SimpleAuth for .NET</strong>.</p>
<hr>
<h2><strong>What is SimpleAuth?</strong></h2>
<p><strong>SimpleAuth for .NET</strong> is a free, open-source solution that makes implementing <strong>user and role-based authentication</strong> simple and fast for .NET WebAPI applications.</p>
<p>It’s built for <strong>small to medium-sized businesses</strong> and developers who want to:</p>
<ul>
<li><strong>Skip the complexity</strong> of ASP.NET Core Identity.</li>
<li><strong>Avoid expensive identity providers</strong> for basic auth needs.</li>
<li>Get started quickly with <strong>local accounts</strong> or <strong>Google SSO</strong>.</li>
</ul>
<p>In short, SimpleAuth lets you focus on <strong>your application’s features</strong>, not the tedious work of setting up authentication.</p>
<hr>
<h2><strong>Why Build SimpleAuth?</strong></h2>
<p>Over my 25 years of software development, I’ve spent <em>weeks</em> wrestling with authentication setups—weeks that should have been spent building valuable features.</p>
<p>Here’s the thing: <strong>auth shouldn’t take weeks.</strong></p>
<p>I built SimpleAuth because I believe:</p>
<ol>
<li><strong>Small projects</strong> shouldn’t require a bloated solution for identity management.</li>
<li>You shouldn’t need to spend hours debugging complex frameworks or tutorials.</li>
<li><strong>Free, self-hosted solutions</strong> are critical for developers and businesses on a budget.</li>
</ol>
<hr>
<h2><strong>What Does SimpleAuth Offer?</strong></h2>
<p>SimpleAuth prioritizes ease of use while delivering essential features:</p>
<ol>
<li><strong>Quick Integration</strong>: Add it to an existing .NET WebAPI project in minutes.</li>
<li><strong>Core Workflows</strong>: Includes login, logout, registration, and password recovery—out of the box.</li>
<li><strong>Local Accounts + Google SSO</strong>: Use built-in local accounts or integrate Google as an identity provider.</li>
<li><strong>User and Role Management</strong>: Manage users and roles without building an entire UI from scratch.</li>
</ol>
<hr>
<h2><strong>SimpleAuth + Your Front-End Framework</strong></h2>
<p>SimpleAuth isn’t just about the back end. To make your life even easier, I’ve started building front-end examples for popular frameworks:</p>
<ul>
<li><strong>Angular (v18):</strong> A polished client example using Angular Material.</li>
<li><strong>React (v18):</strong> A functional implementation that can be extended further.</li>
<li><strong>Vue (v3):</strong> A solid starting point with Vue Router and Composition API.</li>
</ul>
<p>These front-end clients authenticate with the API, handle user workflows, and can serve as blueprints for your own implementation.</p>
<p><img src="/blog/media/simpleauth-login.png"></p>
<p><img src="/blog/media/simple-auth-logged-in.png"></p>
<hr>
<h2><strong>How to Get Started</strong></h2>
<p>Getting started with SimpleAuth is simple (pun intended).</p>
<p>You can find detailed instructions, API documentation, and client setup guides in the <a href="https://github.com/lymestack/SimpleAuth4Net/blob/master/documentation/">documentation</a>.</p>
<hr>
<h3><strong>What’s Next?</strong></h3>
<p>This is just the beginning for SimpleAuth. I’m actively improving it based on feedback and planning to integrate it into my flagship product, <strong>LymeStack</strong>—a modern web application template designed to save developers time and effort.</p>
<p>If you think SimpleAuth is useful, I’d love for you to:</p>
<ul>
<li><strong>Try it out</strong> in your own project.</li>
<li><strong>Contribute</strong> to the project (front-end, back-end, or docs).</li>
<li>Share it with other developers who are tired of struggling with authentication.</li>
</ul>
<hr>
<h3><strong>Final Thoughts</strong></h3>
<p>Authentication is a necessary evil in modern applications, but it doesn’t have to be painful. <strong>SimpleAuth</strong> is here to make implementing auth suck less—so you can focus on what matters: <strong>building great applications</strong>.</p>
<p>Check it out, give it a spin, and let us know what you think.</p>
<hr>
<p>Stay tuned for updates, and thanks for reading!</p>
<p>If you’re curious about <strong>LymeStack</strong> and how SimpleAuth fits into the bigger picture, <a href="https://www.lymestack.com">learn more here</a>.</p>
<hr>
<h3><strong>Want to Contribute?</strong></h3>
<p>SimpleAuth is an open-source project, and contributions are welcome! Whether it’s improving the front-end clients, adding a new feature, or sharing feedback, you can make SimpleAuth even better.</p>
<blockquote>
<p>Follow the project on GitHub and be part of the journey.</p>
</blockquote>
<hr>
<p><strong>Ready to simplify authentication?</strong> Start with SimpleAuth and get back to building. 🚀</p>
]]></description>
      <pubDate>Thu, 12 Dec 2024 20:41:21 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/introducing-simpleauth4net</guid>
    </item>
    <item>
      <title>Reply to Reddit Post titled, "How do you log Error in Database?"</title>
      <link>https://www.lymestack.com/blog/error-logging-reddit</link>
      <description><![CDATA[<p>This blog post is a reply to a Reddit thread I found here:
<a href="https://www.reddit.com/r/dotnet/comments/1fndiqp/how_do_you_log_error_in_database/">https://www.reddit.com/r/dotnet/comments/1fndiqp/how_do_you_log_error_in_database/</a></p>
<p>Here's the original post:</p>
<blockquote>
<p>As the title suggest, I was storing error log in PostgreSQL database using Serilog. But I am not quite satisfied with the result. And Serilog.PostgreSQL NuGet packages are not getting updated.</p>
<p>I want to know how .NET community stores error logs in database. Do you use with EF Core or Serilog. Any suggestion and advice regarding this topic is welcome.</p>
</blockquote>
<p>I intended to paste this markdown as a reply to the post, but I guess it was too long because I got a generic "Unable to create comment" error whn trying to post. Here's my original reply:</p>
<p>I usually just store error data in my centralized ActivityLog table in my database. In order to conserve resources in my DB, I store the error details + stack trace in a text file as JSON. I also make sure all error reports get emailed to an administrator so that we know when errors are happening before they become more widespread. Here’s my code if it helps:</p>
<p>Here's my ActivityLog entity:</p>
<pre><code class="language-csharp">
using System.ComponentModel.DataAnnotations.Schema;

namespace LymeStackApi.Models;

/// &lt;summary&gt;
/// The ActivityLog class is intended to be a centralized logging system
/// for the Application.
/// &lt;/summary&gt;
[Table("ActivityLog")]
public class ActivityLog
{
    /// &lt;summary&gt;
    /// The primary key. It's very conceivable that this field may need
    /// to be changed to a long or bigint data type.
    /// &lt;/summary&gt;
    public int Id { get; set; }

    /// &lt;summary&gt;
    /// The activity being performed. Make sure this doesn't 
    /// value doesn't change for similar activities. Put specifics
    /// in the Notes or Data properties.
    /// 
    /// FUTURE: Will probably refactor this into an ActivityType
    /// table so that it's more efficient and more meaningful.
    /// &lt;/summary&gt;
    public string? Activity { get; set; }

    /// &lt;summary&gt;
    /// Specific details about the activity that area human-readable.
    /// &lt;/summary&gt;
    public string? Notes { get; set; }

    /// &lt;summary&gt;
    /// Sometimes activities have technical details that are only 
    /// interesting to a developer. (e.g. SQL Statements)
    /// &lt;/summary&gt;
    public string? DeveloperNotes { get; set; }

    /// &lt;summary&gt;
    /// The date of occurrence.
    /// &lt;/summary&gt;
    public DateTime DateEntered { get; set; }

    /// &lt;summary&gt;
    /// This column really can contain any data, but is usually 
    /// used to store JSON data and is meant to be machine-readable.
    /// &lt;/summary&gt;
    public string? Data { get; set; }

    /// &lt;summary&gt;
    /// If the activity is related to any particular table or entity 
    /// it can be noted in this column. This makes querying activities 
    /// based on entity much easier since the Data column is not easily
    /// queryable or indexable.
    /// &lt;/summary&gt;
    public string? ReferenceTable { get; set; }

    /// &lt;summary&gt;
    /// If the activity is related to any particular table or entity
    /// the ID of that entity can be stored in this column. Most keys 
    /// in the system are int primary keys, this field is of that type.
    /// In the rare occurence that it might not be, use the Data 
    /// property.
    /// &lt;/summary&gt;
    public int? ReferenceId { get; set; }

    /// &lt;summary&gt;
    /// The ID of the user (in the UserInfo table) that is associated 
    /// with this activity.
    /// &lt;/summary&gt;
    public int? UserInfoId { get; set; }

    /// &lt;summary&gt;
    /// EF Navigation Property
    /// &lt;/summary&gt;
    public UserInfo UserInfo { get; set; }

    /// &lt;summary&gt;
    /// Foreign key to the EmailMessage table.
    /// &lt;/summary&gt;
    public int? EmailMessageId { get; set; }

    #region Constructors

    public ActivityLog(string activity)
    {
        DateEntered = DateTime.UtcNow;
        Activity = activity;
    }

    public ActivityLog(string activity, int? userInfoId)
    {
        DateEntered = DateTime.UtcNow;
        Activity = activity;
        UserInfoId = userInfoId;
    }

    public ActivityLog(string activity, int? userInfoId, string referenceTable, int referenceId)
    {
        DateEntered = DateTime.UtcNow;
        Activity = activity;
        UserInfoId = userInfoId;
        ReferenceTable = referenceTable;
        ReferenceId = referenceId;
    }

    #endregion
}

</code></pre>
<p>In my appsettings.json:</p>
<pre><code class="language-json">
"ApiConfig": {
  "environment": "Production",
  "dataDirectory": "C:/AppData/YourAppDataDirectory/",
  "errorHandling": {
    "emailErrorLogs": true,
    "emailRecipients": "youradmin@yourdomain.net, anotherguy@yourdomain.com",
    "emailSubject": "Automated Error Notification",
    "emailFromAddress": "it@yourdomain.com"
  },
},

</code></pre>
<p>Program.cs:</p>
<pre><code class="language-csharp">
...

var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp =&gt;
{
    exceptionHandlerApp.Run(async context =&gt;
    {
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        context.Response.ContentType = MediaTypeNames.Text.Plain;

        var db = new DataContext(builder.Configuration);
        var apiConfig = builder.Configuration.GetSection("ApiConfig").Get&lt;ApiConfig&gt;();
        var errorLogging = new ErrorLogging(db, apiConfig, context);
        var exceptionHandlerFeature =
                    context.Features.Get&lt;IExceptionHandlerFeature&gt;();

        var ex = exceptionHandlerFeature?.Error;

        var errorData = errorLogging.GetErrorData(ex, context.User.Identity?.Name);
        var errorEmail = errorLogging.SendErrorEmail(errorData);
        errorLogging.CreateErrorActivityLog("Server Error", errorData, context.User.Identity?.Name, errorEmail);

        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        context.Response.ContentType = MediaTypeNames.Text.Html;
        string html = errorLogging.GetMessageBody(errorData).ToString();
        await context.Response.WriteAsync(html);
    });
});

</code></pre>
<p>My ErrorLogging.cs class:</p>
<pre><code class="language-csharp">
using LymeStackApi.Data;
using LymeStackApi.Models;
using LymeStackApi.Models.AppConfig;
using System.Net.Mail;
using System.Text;

namespace LymeStackApi.Code;

public class ErrorData
{
    public string User { get; set; }
    public string ErrorMessage { get; set; }
    public string IpAddress { get; set; }
    public string UserAgent { get; set; }
    public string Url { get; set; }
    public Exception Exception { get; set; }
}

/// &lt;summary&gt;
/// Provides methods to be used for error reporting including the automated error reporting configured in our API.
/// &lt;/summary&gt;
public class ErrorLogging(DataContext db, ApiConfig apiConfig, HttpContext context)
{
    /// &lt;summary&gt;
    /// Creates an error log for an Exception
    /// &lt;/summary&gt;
    /// &lt;param name="ex"&gt;&lt;/param&gt;
    /// &lt;param name="username"&gt;&lt;/param&gt;
    public dynamic GetErrorData(Exception ex, string username)
    {
        var remoteIpAddress = context.Request.HttpContext.Connection.RemoteIpAddress;
        var userAgent = context.Request.Headers["UserInfo-Agent"].ToString();

        var sb = new StringBuilder();
        sb.Append(context.Request.IsHttps ? "https://" : "http://");
        sb.Append(context.Request.Host);
        sb.Append(context.Request.Path);
        if (!string.IsNullOrEmpty(context.Request.QueryString.Value))
            sb.Append(context.Request.QueryString.Value);

        var errorData = new ErrorData
        {
            ErrorMessage = ex.Message,
            User = username,
            IpAddress = remoteIpAddress?.ToString(),
            UserAgent = userAgent,
            Exception = ex,
            Url = sb.ToString()
        };

        return errorData;
    }

    public void CreateErrorActivityLog(string activity, ErrorData errorData, string username, EmailMessage emailMessage)
    {
        var userInfoId = new int?();
        if (!string.IsNullOrEmpty(username))
        {
            var userInfo = db.UserInfos.Single(x =&gt; x.Username == username);
            userInfoId = userInfo.Id;
        }

        var activityLog = new ActivityLog(activity, userInfoId)
        {
            EmailMessageId = emailMessage.Id
        };

        db.ActivityLogs.Add(activityLog);

        try
        {
            db.SaveChanges();
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        // ZOMBIE: We save this to an email message body file, so this is redundant. Commenting for now.
        //var saveFolder = _apiConfig.ErrorHandling.DataDirectory;
        //if (!Directory.Exists(saveFolder)) Directory.CreateDirectory(saveFolder);
        //var savePath = Path.Combine(saveFolder, $"{activityLog.Id}.json");
        //File.WriteAllText(savePath, JsonConvert.SerializeObject(errorData));
    }

    public EmailMessage SendErrorEmail(ErrorData errorData)
    {
        if (string.IsNullOrEmpty(apiConfig.ErrorHandling.EmailRecipients) || !apiConfig.ErrorHandling.EmailErrorLogs) return null;
        var msg = new MailMessage();
        msg.To.Add(apiConfig.ErrorHandling.EmailRecipients);
        msg.From = new MailAddress(apiConfig.ErrorHandling.EmailFromAddress);
        msg.Subject = $"{apiConfig.ErrorHandling.EmailSubject} - {apiConfig.Environment}";
        msg.IsBodyHtml = true;
        msg.Body = GetMessageBody(errorData);

		// Sends an email message and logs to an EmailMessage entity table.
		// NOTE: Since email bodies can be quite large, my email helper stores the body of the email to a text file.
        var emailHelper = new EmailHelper(db, apiConfig.EmailSettings);
        return emailHelper.SendMessage(msg);
    }

    public string GetMessageBody(ErrorData errorData)
    {
        var sb = new StringBuilder();
        sb.AppendFormat("&lt;div&gt;Error Message: {0}&lt;/div&gt;", errorData.Exception.Message).AppendLine();
        sb.AppendFormat("&lt;div&gt;Remote IP Address: {0}&lt;/div&gt;", errorData.IpAddress).AppendLine();

        sb.AppendFormat("&lt;div&gt;UserInfo: {0}&lt;/div&gt;",
            string.IsNullOrEmpty(errorData.User)
                ? "n/a"
                : errorData.User).AppendLine();

        sb.AppendFormat("&lt;div&gt;URL: {0}&lt;/div&gt;", errorData.Url);
        sb.AppendLine();
        sb.AppendLine("&lt;div&gt;EXCEPTION INFO&lt;/div&gt;");
        sb.AppendLine("&lt;hr /&gt;&lt;pre&gt;");
        sb.Append(GetExceptionString(errorData.Exception));
        sb.Append("&lt;/pre&gt;&lt;br /&gt;");

        var innerEx = errorData.Exception.InnerException;
        while (innerEx != null)
        {
            sb.AppendLine("INNER EXCEPTION");
            sb.AppendLine("&lt;hr /&gt;&lt;pre&gt;");
            sb.AppendLine(GetExceptionString(innerEx));
            sb.Append("&lt;/pre&gt;");
            innerEx = innerEx.InnerException;
        }

        return sb.ToString();
    }

    private static string GetExceptionString(Exception ex)
    {
        var sb = new StringBuilder();
        sb.AppendFormat("Source: {0}", ex.Source).AppendLine();
        sb.AppendFormat("Message: {0}", ex.Message).AppendLine();
        sb.AppendFormat("Stack Trace: {0}", ex.StackTrace.Replace("\r", "")).AppendLine();
        return sb.ToString();
    }
}

</code></pre>
<p>There are a few gaps to fill in regards the the EmailHelper mechanism, but hopefully you get the gist of it.</p>
<p>Hope this helps you or anyone else!</p>
]]></description>
      <pubDate>Mon, 23 Sep 2024 17:59:21 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/error-logging-reddit</guid>
    </item>
    <item>
      <title>Y-Combinator HackerNews Show Post</title>
      <link>https://www.lymestack.com/blog/hacker-news-show-post</link>
      <description><![CDATA[<p>I just posted to the early adopter Hacker News Show forum to soft release LymeStack to a small community:
<a href="https://news.ycombinator.com/item?id=41458495">https://news.ycombinator.com/item?id=41458495</a></p>
<p>I'd love to hear your thoughts about my messaging, my pricing or any other feedback you'd like to offer! I have to pass a small threshold of activity in order to appear on the main <a href="https://news.ycombinator.com/show">HackerNews Show Page</a>, so any legit conversation or feedback on the provided link is more than welcome!</p>
<p>Below is the text I drafted prior to removing some extra wording to comply with the 4000 character post limit.</p>
<hr>
<p>TL;DR; - Angular + .NET Core WebApp Template with Full-featured Admin Area and CRUD / Report feature generation tools. Get a responsive web application w/ database integration, authentication and a full-featured admin area up and running on your local machine with a searchable, sortable and exportable CRUD feature in <strong>minutes</strong>.</p>
<p>Website: <a href="https://www.lymestack.com/get-started">https://www.lymestack.com/get-started</a>
Intended Audiences:</p>
<ul>
<li>Independent Freelance Developers / Soloprenuers / Small IT Companies</li>
<li>IT Managers of Small-to-Medium Sized Businesses (SMB’s) - CIO / CTO / Director of Digital Development</li>
</ul>
<p>Hi, I’m Mike and I’m an Angular + .NET freelance developer with about 25 years in the web-app development business. Over the years, I’ve built myself some crude console applications that automate a lot of the boring and time consuming parts of my job: getting new apps started and developing CRUD admin features. These tools have allowed me to remain competitive with larger web development firms because I can provide my clients with more affordable web applications faster and cheaper than they thought possible. I thought that if these tools are saving me so much time, then they’ve got to be of use to others as well. That’s why I decided to refine my tools for the public by building LymeStack.</p>
<p>LymeStack consists of two main components that save me time almost every day:</p>
<h2>Full-Stack Web Application Template with Admin Area</h2>
<p>LymeStack comes with a standard web application template that allows a developer to scaffold a new app in just minutes for a new client or project. This web app starter template comes with a stable, feature-rich admin area prebuilt, allowing your client’s administrators to have a large degree of control and transparency in their web application out-of-box. This ability for your clients to self-service common tasks reduces interruptions in your work day by substantially decreasing support emails and phone calls.</p>
<p>The template and admin area are filled with lots of goodies that would normally need to be built from scratch: user management / role assignment, SSO authentication, audit-logging, error logging, app health checks… <a href="https://www.lymestack.com/blog/getting-started-is-the-hardest-part">the list goes on</a>. All of these features combined would take a single experienced developer <strong>at least a month</strong> to build. LymeStack offers exponentially more than any other half-stack (front-end only) template or starter tutorial that I’ve come across so far and serves as a solid starting point, enabling developers to get to work right away on their client’s core concerns.</p>
<h2>Automated CRUD / Reporting Feature Generation</h2>
<p>This template is accompanied by a “Scaffold Feature” tool that automates much of the time-consuming, error-prone, tedious work involved when creating CRUD admin features and reports. The tool will generate standard Angular and C# code from existing database tables or stored procedure output, making it useful for both new feature development and system modernization projects. In fact, many of the LymeStack tools originated specifically to help modernize an old Crystal Reports / ASP Classic system and have since gone on to help modernize other legacy .NET Framework applications.</p>
<p>On average, 1000 lines of code (and around 30 different Angular / .NET C# files) are generated per each scaffold operation. This number can increase significantly depending on the number of fields that exist in the table that you’re working with. Imagine how long it would take to write that amount of code from scratch. The increase in productivity is significant!</p>
<p>These tools generate standard open source code (<strong>not</strong> proprietary to LymeStack) that any Angular or .NET developer should feel comfortable customizing using standard free tools such as Visual Studio Code. LymeStack focuses on readability and code consistency to make more maintainable applications that can be supported by more than just one developer. Additionally, licenses on any code generated from commercial or community licenses are perpetual, allowing you to continue using the web application as you please without being beholden to a subscription after you are through with using the tools.</p>
<p>LymeStack is free to try for 30 days. Check out my website at <a href="https://www.lymestack.com">https://www.lymestack.com</a> to get started.</p>
<h3>Disclaimer</h3>
<p>You will see a security warning in your web browser when trying to download and from Windows Defender when trying to install the program. Per <a href="https://www.reddit.com/r/dotnet/comments/1f90pi1/security_warnings_on_legit_downloadable_software/">this Reddit thread</a>, I’m working through this.</p>
<p>If you are skeptical and would still like to evaluate the software, I recommend you use a virtual machine or Windows Sandbox to try it out.</p>
]]></description>
      <pubDate>Thu, 05 Sep 2024 18:27:44 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/hacker-news-show-post</guid>
    </item>
    <item>
      <title>Python Script to Assemble a Combined Markdown File from Multiple Markdown Files</title>
      <link>https://www.lymestack.com/blog/combined-markdown-assembly</link>
      <description><![CDATA[<p>All of our documentation in the <a href="https://www.lymestack.com/docs.html">LymeStack Product Manual</a> is authored using standard text-based Markdown files using <a href="https://code.visualstudio.com/">Visual Studio Code</a> and the <a href="https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced">Markdown Preview Enhanced</a> VS Code plug-in, which offers a preview of the Markdown output to the side while editing.</p>
<p><img src="/blog/media/vscode-markdown-plugin.png"></p>
<p>I have a centrally located README.md file that serves as my document index containing a link to every other markdown file in the folder or any subfolders. We used this documentation to submit as part of our provisional patent application that we filed with the US Patent and Trademark Office (USPTO). In order to submit the document to the USPTO, we first had to convert it to a combined PDF. In order to do this, we execute this Python script in the same folder as the README file:</p>
<pre><code class="language-python">import os
import sys
import markdown2

def adjust_image_paths(content, current_file_dir, output_base_dir):
    """
    Adjust relative image paths to be relative to the output base directory and use forward slashes.
    """
    lines = content.split('\n')
    for i, line in enumerate(lines):
        if '](' in line:
        #if '![' in line and '](' in line:
            start_link = line.find('](') + 2
            end_link = line.find(')', start_link)
            relative_path = line[start_link:end_link]
            if not os.path.isabs(relative_path):
                # Calculate the relative path from the current file directory to the image
                image_path = os.path.normpath(os.path.join(current_file_dir, relative_path))
                relative_to_output = os.path.relpath(image_path, output_base_dir)
                # Convert backslashes to forward slashes
                relative_to_output = relative_to_output.replace('\\', '/')
                lines[i] = line[:start_link] + relative_to_output + line[end_link:]
    return '\n'.join(lines)

def assemble_markdown_files(index_file, output_file_base):
    markdown_content = ""
    toc_content = ""
    base_path = os.path.dirname(os.path.abspath(index_file))
    output_base_dir = os.path.dirname(os.path.abspath(output_file_base + '.md'))

    with open(index_file, 'r') as file:
        lines = file.readlines()
    
    for line in lines:
        if ' [' in line:
            start_link = line.find('(') + 1
            end_link = line.find(')')
            file_link = line[start_link:end_link].strip()
            file_path = os.path.normpath(os.path.join(base_path, file_link))

            # Generate clean anchors for ToC links
            anchor_link = f'#{os.path.basename(file_link).replace(".md", "").replace(" ", "-").lower()}'
            toc_line = line.replace(file_link, anchor_link)
            toc_content += toc_line
            
            # Check file type and skip non-markdown files
            if file_link.lower().endswith('.md') and os.path.exists(file_path):
                with open(file_path, 'r') as infile:
                    file_contents = infile.read()
                    file_contents = adjust_image_paths(file_contents, os.path.dirname(file_path), output_base_dir)
                    file_contents = file_contents.replace("\n#", "\n##")  # Downgrade headers
                    markdown_content += f"\n&lt;a id='{os.path.basename(file_link).replace('.md', '').replace(' ', '-').lower()}'&gt;&lt;/a&gt;\n\n----\n\n"
                    markdown_content += file_contents + '\n'
            elif not file_link.lower().endswith('.md'):
                toc_content += f" (External file not included in compilation)\n"
            else:
                markdown_content += f"# File not found: {file_link}\n\n"
        else:
            toc_content += line
            markdown_content += line

    complete_markdown = toc_content + markdown_content
    with open(output_file_base + '.md', 'w') as outfile:
        outfile.write(complete_markdown)

if __name__ == "__main__":
    index_filename = 'README.md'
    output_filename_base = 'Complete'
    
    if len(sys.argv) &gt; 1:
        index_filename = sys.argv[1]
    if len(sys.argv) &gt; 2:
        output_filename_base = sys.argv[2]
    
    assemble_markdown_files(index_filename, output_filename_base)
</code></pre>
<p>To use this script, from the folder where the script and readme file and other markdown files reside, run the following command in your terminal window or command prompt:</p>
<pre><code class="language-bash">python assemble-mardown-files.py
</code></pre>
<p>You will see a new file created as a result of running the above command called <code>Complete.md</code>. If you open that file using the VS Code plugin and right-click anywhere in the preview window, you will see an "Open in Browser" option.</p>
<p><img src="/blog/media/vscode-markdown-plugin-open-in-browser.png"></p>
<p>Clicking that option will open the combined PDF as and HTML web page. From there, you can use your browser's print feature to print this output to a PDF document, which will result in a professional looking and navigable / searchable PDF output. This final output was what was submitted to the USPTO as a specification document.</p>
]]></description>
      <pubDate>Thu, 29 Aug 2024 14:41:02 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/combined-markdown-assembly</guid>
    </item>
    <item>
      <title>Vacation-Proof Your Application</title>
      <link>https://www.lymestack.com/blog/vacation-proof-your-application</link>
      <description><![CDATA[<blockquote>
<p>If you're checking emails, you're not on vacation<br>
- Randy Pausch, Author of <a href="https://en.wikipedia.org/wiki/The_Last_Lecture">The Last Lecture</a></p>
</blockquote>
<p>This quote came from Randy Pauch (RIP), who became famous for his book and Oprah appearance as a dying professer who spent his last year on this planet in a true state of reflection. He gave a separate talk called <a href="https://www.youtube.com/watch?v=oTugjssqOT0">Time Management</a>, which is one I revisit from time to time as it contains a lot of good tid-bits of information worth retaining. I recommend it to anyone and everyone!</p>
<hr>
<p>There are many benefits to being a soloprenuer. I've been doing it for the majority of my near 25 year-long career. As a soloprenuer, you can set your own schedule, choose your best clients (and fire your worst clients), and really be regarded as an expert in your field. Of course every scenario in the world has its advantages and drawbacks and one of those major drawbacks of being a soloprenuer is that, a soloprenuer can never really take a full vacation, but there are ways of making it so that if you do go away on holiday, you can make the most of it all while still keeping a finger on the pulse of your business.</p>
<p>Over the many years of being a soloprenuer, I have been able to go away on several vacations and there be no incident whatsoever. Sure, on occasion an emergency or circumstance will happen where I'll have to work to fix something, but if that happens, being prepared is key to being able to resolve the issue as quickly as possible so that I could return to my vacation.</p>
<h2>Before Vacation</h2>
<h3>Plan Ahead</h3>
<p>Effective planning is key to a stress-free vacation. Start by identifying your business's slow season and schedule your vacation during this time. Work ahead on projects and meet all deadlines before you leave. For time-sensitive tasks, consider scheduling them to be published or delivered while you're away. If disconnecting for more than a day at a time is not practical or responsible, consider vacation destinations where a stable internet connection is available where you are staying or near-by. Cruise ships are often not a practical choice because Internet packages are often exorbonant. You might have to put on hold any ambition to be too far away until you have partners or employees to cover for you while you are gone.</p>
<h3>Giving Your Primary Clients Notice</h3>
<p>Set expectations with your clients well in advance. Inform them of your upcoming vacation and provide clear instructions on who to contact in case of urgent matters. This transparency helps maintain trust and ensures your clients aren't caught off guard.</p>
<h3>Define "Emergency" and Special Rates</h3>
<p>Clearly outline what constitutes an emergency with your clients. Establish a special rate for dealing with these emergencies while on vacation. This approach discourages non-essential interruptions and compensates you fairly if you need to work during your time off.</p>
<h3>Rely on a Backup Contact</h3>
<p>Even as a non-soloprenuer, when you go on vacation, you usually have someone you trust stop by to feed the pets, water the plants or pickup your mail. Similarly as a soloprenuer, you may have a small network of trusted colleagues, one of whom that you could give termporary access to your network to subcontract for you and field support calls. Give them temporary access to the systems or git repositories that they may need access to while you are gone. That said, while trust is a good thing to have, it's probably good to have at least a mutual <a href="https://en.wikipedia.org/wiki/Non-disclosure_agreement">non-disclosure agreement</a> or even better a <a href="https://legaltemplates.net/form/employment-contract/subcontractor/software-development/">subcontractor agreement</a> in place with any subcontractors that you might have as your backup.</p>
<h3>Prepare Your Network</h3>
<p>Make your work network accessible via the Internet via a secure VPN connection. My main development machine is a headless virtual machine (VM) on my network that I connect to using <a href="https://support.microsoft.com/en-us/windows/how-to-use-remote-desktop-5fe128d5-8fb1-7a23-3b8a-41e636865e8c">Remote Desktop</a> from any other machine on my network. Since I work out of my home, I use an inexpensive <a href="https://www.raspberrypi.com/">Raspberry Pi</a> and <a href="https://www.wireguard.com/">WireGuard</a> to give myself secure access to my VPN any time I am away. <a href="https://www.pivpn.io/">The PiVPN Project</a> makes setting this up as easy as it can get. It will be important for you to observe your home's IP address. My IP address is dynamic but rarely changes. If you are worried about your IP address changing while you are away, you could consider some sort of <a href="https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/">dynamic DNS service</a>.</p>
<p><strong>TIP:</strong> Don't directly expose RDP to your machines from externally mapped ports. This is one of the single biggest risks in the cyber security space for which you could be denied <a href="./cyber-insurance">cyber liability insurance coverage</a>.</p>
<h3>Prepare Your Laptop or Tablet</h3>
<p>When traveling, you need to do a little extra due-diligence to make sure you stay connected and keep yours and your clients' data safe.</p>
<h4>Clean Up and/or Encrypt Your Device</h4>
<p>Don't keep anything on your laptop that is sensitive or important. If you do, make sure it is encrypted in case your laptop gets stolen or lost while you are traveling. It's also good to make use of built in file system encryption such as <a href="https://en.wikipedia.org/wiki/BitLocker">BitLocker</a> if you are on Windows or <a href="https://support.apple.com/guide/deployment/intro-to-filevault-dep82064ec40/web">FileVault</a> if you're on macOS. Sometimes you might anticipate periods of disconnectivity and still want to work. In these cases, I would recommend that you only copy what is needed to your laptop (e.g. cloning a git repo) to be able to work on. For added security, I would store these files in a local VeraCrypt valut. <a href="https://www.veracrypt.fr/">VeraCrypt</a> is an open-source, cross-platform application that allows you to mount encrypted volumes that are protected by a password of your choosing. Between that and an encrypted file-system, I would think you could be considered to be pretty safe, but you might consult a security expert to double-check this if the data you carry is extra sensitive. For example, HIPPA regulations may prohibit you from carrying sensitive patient data on a portable device.</p>
<p>Personally, I don't travel with a laptop anymore. I use an older iPad Pro and <a href="https://www.apple.com/ipad-keyboards/">keyboard</a> to connect to my work network using a mobile hotspot on my phone, a VPN and Remote Desktop to connect as described previously. The fact that the device is older and contains nothing sensitive makes me worry a little less about theft, damage or loss. Cellular data or public WiFi hotspots are increasingly abundant and in-flight WiFi support is more commonly supported than not if you are flying (and RDP has worked fine in-flight for me as well).</p>
<h4>International Travel Considerations</h4>
<p>If you're traveling internationally, research SIM card options so that your tablet or phone's mobile hotspot can connect to your destination country's cellar data services. I recently traveled to Portugal and I was able to get a data-only SIM card from <a href="https://www.vodafone.com/">Vodafone</a> for about 20 Euros (1 Euro at the time was exchanged the the US Dollar at $1.09, so about $22 USD) for the entire 2 weeks I was there. If you bought / financed your cell phone or tablet through a carrier, it's likely that your device is locked and any outside SIM from another carrier will not work. Make sure to call your carrier at least a month before your trip in to ensure your device is unlocked. For some reason, it takes a while for an unlock application to go through. If your device is newer than a year old, it's likely your carrier could deny your request to unlock the device, in which case your carrier probably offers an International plan, currently at approximately $10 per day. If you want to avoid the high cost of an international plan, you can always use an older unlocked phone as your hotspot. If your old mobile device was purchased through a carrier it is likely still locked, but if it is paid off, your carrier has to unlock it (or at least are willing to unlock it, in my experience).</p>
<p><strong>Afterthought:</strong> Make sure to pack your chargers and portable battery charger. You'll have to carry anything containing a battery in your personal bag or carry on since it's against FAA regulations to check any luggage containing lithium-ion batteries.</p>
<p><strong>Another Afterthought:</strong> If you had to enable an international plan on a locked cell phone, make sure that your celluar provider turns this feature off. My parents accidentally forgot to turn this off after a trip to Europe and got charged unnecessary overages for which they had to call their carrier to get the charges reversed.</p>
<h3>Test Your Connection</h3>
<p>BEFORE YOU LEAVE test your mobile setup. <strong>Give yourself ample time</strong> in case you need to debug any connection issues if it doesn't work on your first attempt. If you're using a laptop or non-cellular table, disable your home's WiFi and connect to your phone's mobile hotspot. If your tablet is cellular enabled, skip this and just disable wifi on your tablet. From there, connect to your VPN using the <a href="https://www.wireguard.com/install/">WireGuard App</a> (available on all major mobile and desktop operating systems) and then use a Remote Desktop App (built into Windows or <a href="https://apps.apple.com/us/app/microsoft-remote-desktop/id1295203466?mt=12">available for macOS here</a>) to connect to your network's resource(s). If you can successfully connect, you're ready to start packing.</p>
<h2>During Vacation</h2>
<p>Notice this section is short. Hopefully by this point you'll have done all your preparation and if you're lucky, you won't even need to open a laptop...</p>
<h3>Morning Check-In</h3>
<p>Dedicate a short period each morning to check in on your business. This could involve reviewing emails, ensuring automated systems are functioning correctly, or addressing any urgent matters. Limit this time to as little as possible to get off your device and back to your vacation.</p>
<h3>Turn Notifications OFF</h3>
<p>To truly disconnect and enjoy your time off, turn off non-essential notifications on your devices. This helps prevent constant interruptions and allows you to focus on relaxation and rejuvenation.</p>
<h3>ICE</h3>
<p>IN CASE OF EMERGENCY - As a soloprenuer, you really need to have a way to connect in the rare occurrence that an emergency is taking place. Have a way for your client or backup contact to reach you. Again, it's very important that you and your clients have a true understanding as to what the definition is of "an emergency" in order to avoid any unnecessary interruptions.</p>
<h3>Balance Work and Relaxation</h3>
<p>If you must work during your vacation, try to balance it with relaxation. Consider working early in the morning to minimize disruption to your vacation activities.</p>
<h3>RELAX and <em>ENJOY YOURSELF</em></h3>
<p>You've undoubtedly earned it. Cheers!</p>
<h2>After Vacation</h2>
<h3>Thank your Backup Contact</h3>
<p>Show appreciation to the person who helped manage your business while you were away. A sincere thank-you note or small gift can go a long way. Offer to return the favor when it's their turn to go on vacation if you can.</p>
<h3>Consider Re-Restricting Access</h3>
<p>After returning, review and adjust any temporary access you granted to your systems or accounts during your absence.</p>
<h3>Review and Reflect</h3>
<p>Take time to review how your business performed in your absence. Identify any areas that need improvement and consider implementing changes to make future vacations even smoother.</p>
<h2>Ongoing Considerations...</h2>
<h3>Always turn every admin request into an admin feature</h3>
<p>Ask yourself: Is what I'm doing a developer task or an administrative task? If the answer is the latter, build a feature so that your customer can do it themselves next time. This approach not only saves you time in the long run but also empowers your clients and makes your product more user-friendly.</p>
<h3>Continuously Improve Your Systems</h3>
<p>Regularly assess and improve your business processes, automation tools, and delegation strategies. The more efficient and self-sustaining your business becomes, the easier it will be to take time off in the future.</p>
<h3>Prioritize Self-Care</h3>
<p>Remember that taking time off is crucial for your well-being and the long-term success of your business. As John Branch IV, a successful solopreneur, advises, "We need to prioritize ourselves first before our business, because we are our businesses' greatest asset".</p>
<p>By implementing these strategies, you can create a more vacation-friendly solopreneur business. While you may not be able to completely disconnect, you can certainly enjoy a more relaxing and rejuvenating time off, knowing that your business is running smoothly in your absence.</p>
<h2>Check out LymeStack</h2>
<p>If you found these tips useful, check out my web application template and developer toolset called LymeStack. I built LymeStack as a single soloprenuer to serve my own needs by allowing me to build the foundation for my client's web applications in a very short amount of time. The toolset also helps me to eliminate hours-upon-hours of repetivive CRUD coding and allows me to get to more interesting tasks that my clients are excited about. The template contains lots of built in self-service administrative functionality and automated error reporting features, making it easier for my clients to fulfill most of their own requests and reduces phone calls and emails, allowing me the bandwidth to actually take a vacation! To find out more about the types of problem our LymeStack Template solves, check out the blog post <a href="./getting-started-is-the-hardest-part">Getting Started is the Hardest Part</a>.  LymeStack is free to evaluate for 30-days. For more information check out our <a href="https://www.lymestack.com/get-started">Getting Started</a> guide to get a working app up and running on your local computer in about an hour.</p>
<p>Thanks for reading!</p>
]]></description>
      <pubDate>Wed, 04 Sep 2024 16:00:17 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/vacation-proof-your-application</guid>
    </item>
    <item>
      <title>There's a Lot to Web Development</title>
      <link>https://www.lymestack.com/blog/theres-a-lot-to-web-development</link>
      <description><![CDATA[<blockquote>
<p>If the only tool you have is a hammer, you tend to see every problem as a nail”<br>
- Abraham Maslow</p>
</blockquote>
<p>Back in 1996, when I started learning about the web and HTML, it was a very different time from what it is today. There was really only HTML... CSS was only used for typography and Javascript was mostly only used for simple form validation. This afforded me the opportunity to watch the technology grow. While it was still very fast paced, the emerging tech allowed me to digest the new information in bite-size portions.</p>
<p>Today, things are much different. I can imagine how overwhelming it must be to enter this technology sector with little to no experience. There are countless languages and frameworks to choose from, all with their own advantages and disadvantages. Here at LymeStack, we have our own opinions as to the tech stack we use to build and maintain our projects for ourselves and our clients. This blog post will try to give an overview of those decisions as well as the reasoning behind them.</p>
<p><strong>Disclaimer:</strong> I'm <em>mostly</em> a Microsoft guy in terms of development. While I do stray from the reservation on occasion, it's important to <strong>use the right tool for the job</strong> and I've found that Microsoft's development tools and large support ecosystem have been the right path for me.</p>
<p><strong>Note:</strong> I'm not being paid by any of the following vendors to promote their products... though I should be!</p>
<h2>Topics Covered</h2>
<p>Here's a complete inventory of the tech-stack that makes LymeStack tick.</p>
<ul>
<li><a href="#back-end-technology-net-8-webapi">Back End Technology: .NET 8 WebApi</a></li>
<li><a href="#front-end-app-angular">Front End App: Angular</a></li>
<li><a href="#front-end-ui-libraries-angular-material--bootstrap">Front End UI Libraries: Angular Material / Bootstrap 5</a></li>
<li><a href="#database-microsoft-sql-server">Database: Microsoft SQL Server</a></li>
<li><a href="#authentication-service-microsoft-azure-entra-id">Authentication Service: Microsoft Azure Entra ID</a></li>
<li><a href="#icon-library-fontawesome">Icon Library: FontAwesome</a></li>
<li><a href="#marketing-website-html--razor-cshtml--jquery--vue">Marketing Website: HTML / Razor (CSHTML) / jQuery / Vue</a></li>
<li><a href="#cross-platform-desktop-application-blazor">Cross Platform Desktop Application: Blazor</a></li>
<li><a href="#blogging-software-orchardcms">Blogging Software: OrchardCms</a></li>
<li><a href="#documentation-markdown">Documentation: Markdown</a></li>
<li><a href="#installer--setup-builder-inno-setup">Installer / Setup Builder: Inno Setup</a></li>
<li><a href="#production-cloud-platform-azure">Production Cloud Platform: Azure</a></li>
<li><a href="#local-virtualization-platform-hyperv-on-windows-or-virtualbox">Local Virtualization Platform: HyperV on Windows</a></li>
<li><a href="#credit-card-processing-stripe">Credit Card Processing: Stripe</a></li>
<li><a href="#domain-names-godaddy--squarespace-formerly-google-domains">Domain Names: GoDaddy &amp; SquareSpace (formerly Google Domains)</a></li>
<li><a href="#favicon-generator-real-favicon-generator">FavIcon Generator: Real Favicon Generator</a></li>
<li><a href="#smtp-delivery-service--postmark">SMTP Delivery Service</a></li>
</ul>
<h3>Back End Technology: .NET 8 WebAp</h3>
<p>[Microsoft's .NET] technology supports a few languages, but by far the leading language in their ecosystem is C-sharp (C#), a language developed by Microsoft. C# has been around for quite a while, first appearing in the .NET Framwork that originated in the early 2000's. Microsoft has since replaced the .NET Framework with .NET Core and most recently branded. Hedging from the topic that <a href="./ms-sucks-at-naming">Microsoft sucks at naming/renaming things</a>, their most recent iteration of C# is the language we use to power our back-end API for our entire organization. It's a mature technology and works seamlessly with Entity Framework, the ORM technology also developed by Microsoft that is used to interact with database objects such as tables, views, and stored procedures. Unlike it's predecessor, .NET 8 now runs cross-platform, so you can develop the API on any operating system that you like. I still like to develop on Windows, but it's nice to have the choice.</p>
<h3>Front End App: Angular</h3>
<p><a href="https://angular.io">Angular</a> is a Javascript based full-featured framework built and maintained as an open source project by Google first released in 2010. If Microsoft offered a Javascript framework, I'd probably use it, but my alignment with Angular is a natural progression from my first real start with Single Page Application (SPA) programming using it's predecessor AngularJS (aka Angular 1). AngularJS was a game changer and it really hit home and laid a solid foundation for what modern web application programming looks like today. Sure, credit should be given to a lesser known library called KnockoutJS, but I think AngularJS made a larger impact in the web app development world. Unfortunately, AngularJS showed its inability to scale as applications became larger and more complex causing performance issues for users and old hardware.</p>
<p>Then in 2016 came Angular 2. Angular 2 was a complete rewrite of Angular 1, which addressed many of the shortcomings of its predecessor and it's new structure still very much resembles modern versions of Angular (version 18 at the time of this writing). This rewrite created a couple problems though:</p>
<ul>
<li><p><strong>Need to Modernize AngularJS Apps</strong> - This introduction of Angular 2 left a bitter taste in the mouth of us legacy AngularJS developers. This complete abandonment of AngularJS felt like a slap in the face because we were so invested and comfortable with AngularJS aside from it's performance problems (albeit I still miss the conveniences of $broadcast and $emit). The added fact that it was a complete rewrite let a feeling like we were starting over from scratch again... and we were... I imagine the Angular community lost a lot of developers to other competing frameworks like React and Vue because of this drastic change. Anyway... <a href="https://blog.codinghorror.com/version-1-sucks-but-ship-it-anyway/">Version 1 of any app usually always sucks</a>, and they had a good run for a 1.0 version, so any bitterness or surprise is a moot point.</p>
</li>
<li><p><strong>Increased Learning Curve</strong> - Angular by default is a pretty large and opinionated framework. It's not very well suited for small and simple applications, but it has it's place in larger and more complex applications or small applications that would potentially grow into something more complex. This high level of opinionation can provide a more unified approach to development as more developers enter the mix. We also pay attention and try to incorporate best practices to stay on the same page. <a href="https://www.pluralsight.com/courses/best-angular-practices">This Pluralsight course</a> is a great piece of work and we heavily followed this guide in building our LymeStack front end web app template.</p>
</li>
</ul>
<p>Note: We are currently considering creating a "React Edition" of our web app template. If this is something that interests you, please <a href="https://www.lymestack.com/#contact">contact us</a></p>
<h3>Front End UI Libraries: Angular Material / Bootstrap</h3>
<p>This is probably not considered the best practice, but f--- it. The LymeStack front end web app uses BOTH Angular Material and Bootstrap 5 in its UI implementation and design.</p>
<p>Why Angular Material? <a href="https://material.angular.io/">Angular Material</a> was built by the Angular team, implementing the concepts of <a href="https://m3.material.io/">Material Design</a> seamlessly into the an Angular based web app. It's documentation is pretty good and the code implementation is fairly terse and compact. (Less moving / working parts the better) It also offers a comprehensive UI component library for any common UI/UX design scenario. Angular Material does have some drawbacks though, which is causing us here at LymeStack to explor other alternatives. <a href="https://www.lymestack.com/#contact">(We'd love to hear from anyone who has an opinion on this)</a> The main drawback is that Angular Material did a major rewrite of its framework, breaking many of my CSS customizations that improved the look and feel of the sterile default implementation of Angular Material offered. This has us stuck on Angular 14 (now on 18) and is a current admitted shortcoming about our product that we are trying to solve. The other drawback, which might be considered to be a positive to others, is that being designed by Google, Angular Material naturally looks a lot like a native Android app. I'm an iPhone guy, so this isn't the best for my preferences. It's because of these drawbacks that have us looking into other frameworks like <a href="https://ng-bootstrap.github.io/">ngBootstrap</a>, <a href="https://valor-software.com/ngx-bootstrap/">ngx-Bootstrap</a> or <a href="https://valor-software.com/ngx-bootstrap/">PrimeNG</a>.</p>
<p>Why Bootstrap? As of the writing of this post, <a href="https://getbootstrap.com/docs/5.3/getting-started/introduction/">Bootstrap 5</a> is the latest and greatest version of the popular Bootstrap library originated at the formerly known Twitter (now <a href="https://www.x.com">X.com</a>. I still think I prefer the look of the UI components offered by Bootstrap over Material Design and implement them on occasion such as <a href="https://getbootstrap.com/docs/5.3/components/alerts/">alerts</a> and <a href="https://getbootstrap.com/docs/5.3/components/badge/">badges</a>. We still also prefer Bootstrap's <a href="https://getbootstrap.com/docs/5.3/layout/grid/">grid system</a> for building responsive column layouts.</p>
<h3>Database: Microsoft SQL Server</h3>
<p>I was introduced to <a href="https://azure.microsoft.com/en-us/">Microsoft SQL Server</a> back in 1999 at my first job out of college and I was an instant fan. Up until then, I had only one real database course in Microsoft Access in college. I liked SQL and it made sense to me, but everything else about Access was loathable. Around the same time, I also had some production experience with mySQL and Oracle. They were good database engines, but what really set SQL Server apart is the <a href="https://learn.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16">SQL Server Management Studio (SSMS)</a> tools, which in my opinion, still gives Microsoft SQL Server the edge for me today. It has the expected tools you would use to design tables and query / manipulate data, but also has some cool diagraming tools that allow me to create ER (Entity-Relationship) diagrams as I am thinking about the overall schema. <del>When I'm done diagramming the schema, there's a "Generate Change Script" option, which will allow for me to generate a T-SQL file with all my diagrammed changes for easy deployment to my test and production environments. Another added feature that SSMS / SQL Server has is a performance tuning wizard, which will capture queries run on a database (a trace), analyze the queries and recommend indexes that can increase performance significantly on slow running queries.</del></p>
<p>Microsoft SQL Server comes in a few editions. Microsoft SQL Server Express is the free version of the product. It's free to use for production purposes for databases up to 10 GB in size, which if you program efficiently and have strategies to keep your database lean, can go a long way. I once built a system that was doing up to a million dollars in business per month by the time it sold, had been running for about 5 years, and its uncompressed size was still less than 500 MB. SQL Server Express doesn't have the diagramming support or performance tuning tools, so I only use SQL Express in the test and production environments. If I need to debug a production issue with queries, I copy the production database to my local development environment where I have those tools to me via SQL Server Developer Edition, which I'll explain in a moment. SQL Express also has some limitations around CPU core usage and memory... again, all limits I've never encountered myself.</p>
<p>Once you exceed the 10GB limit or need more advanced features, that's when paid commercial versions of SQL Server come into play. Odds are that if you get to the point where you need that sort of horsepower, you likely have the budget to pay for these licenses. For a full list of differences between the editions, you can <a href="https://chatgpt.com/share/2fd41e14-45dd-4fe7-9582-0f9b68cd64ea">see this breakdown that ChatGPT summarized for me</a>.</p>
<p>On my local computer, where I have all my development tools, I run SQL Developer Edition, which is a full featured instance of SQL Server with all the diagramming and performance tuning goodies, but is only intended for development purposes and not licensed to run in production environments. This is why when I need to do performance tuning, I bring down a full copy of the prod database and debug locally.</p>
<p>Lastly, SQL Server can now run natively on Linux or on macOS with the help of Docker. Use still need Windows to run SSMS, but other native tools such as Navicat are available as native GUI's for macOS. I'm not really sure what's out there for Linux, but there's probably something.</p>
<p>It's also worth mentioning that I've had my eye on PostgreSQL for quite some time, but the tooling I've seen that comes with it so far has not exceeded the level of functionality that I get from SSMS, at least in the form of free tools.</p>
<h3>Authentication Service: Microsoft Azure Entra ID</h3>
<p>In my blog post, <a href="./getting-started-is-the-hardest-part">Getting Started is the Hardest Part</a>, I describe how authentication / authorization is the worst part about web application development for me. Unfortunately, it's one of the first things you have to implement when building a new app. This cumbersome, unavoidable and often painful step is a necessary evil. In the past, we used to create local user account tables using the ASP.NET membership provider and that was about as simple as it gets. Unfortunately, in today's security landscape and as cyber attacks become more sophisticated, using local accounts are becoming more of a liability.</p>
<p>To solve this issue, we need some outside help from a third parth OAuth 2 provider. These providers most notably add MultiFactor Authentication (MFA) support to your application by allowing your users to log in with their username/password and SMS message or authenticator app.</p>
<p>There are many options to chose from, but I reluctantly decided to chose Microsoft Azure's Entra ID after what seemed to be weeks of chipping away at it and navigating outdated tutorial after tutorial until I finally <a href="https://www.c-sharpcorner.com/blogs/azure-ad-integration-with-angular-and-asp-net-core-web-api">found one that got it right</a>. I say reluctantly because of the level of effort it took to get working and it goes against my sentiment, <a href="./f-the-cloud">F@#* the Cloud</a>. It's on the roadmap to implement a few alternatives so as to not be dependent on any one auth provider. Of course there is always <a href="https://www.auth0.com">Auth0</a>, which is the best in my opinion, but I made the decision to only implement free options into my product template so I'm not forcing my customers to pay a 3rd party in order to be able to use my product. Ultimately, someday I would like to include the ability to use local accounts in combination with any major third party provider, including Auth0. If my customers <em>want</em> to pay for Auth0, then great, but I don't want to force the issue.</p>
<h3>Icon Library: FontAwesome</h3>
<p>I have been using <a href="https://www.fontawesome.com">FontAwesome</a> for a good many years, participating in their kickstarter campaign many years back, which bought me a good few years of free use of their icon library. FontAwesome provides a wide array of solid icons that can be use for free and offer paid options for special use cases and other variations of their product like multi-toned or light implementations. Some other free libraries I've discovered and use on our marketing website are <a href="https://iconscout.com/unicons/free-line-icon-fonts/">Unicons</a> and <a href="https://pictogrammers.com/library/mdi/">Material Design Icons</a>.</p>
<p>Using these free icon libraries will save lots of time and money in hiring custom graphic designs (sorry designers), drastically reduce the amount of images one has to manage, and most importantly, they add a nice and professional touch to a web application.</p>
<h3>Marketing Website: HTML / Razor (CSHTML) / jQuery / Vue</h3>
<p>What?? All this self-promotion and selling the Kool Aid about LymeStack and I'm not using it for our marketing website? Well, yes and no...</p>
<p>Yes, in that LymeStack is used as the API for the marketing website. I use LymeStack to manage the data that is served from the API. It also handles form submissions and the downloadable assets for the developer tools. As I pointed out before, it's important to <a href="./use-the-right-tool-for-the-job">use the right tool for the job</a> and here is where I practice what I preach.</p>
<p>No, in that the marketing website doesn't require the added overhead that's included with the full-blown back office application. Instead we have a lean, efficient and most importantly, SEO friendly maketing presense. We simply link to our LymeStack app to a subdirectory on our website. Currently the marketing site uses mostly static HTML and use of jQuery to handle any API interaction or functionality like form validation. The Video Library is also a small Vue app that grabs its data from the API. To consolidate technology, I will probably convert the marketing site to Blazor in the near-ish future.</p>
<p><strong>Tip</strong> - If any part of your app is SEO sensitive, don't use Angular or React. Search engine robots have a near impossible time (unless technology has improved in some way that I'm not aware of) with crawling content served from a Javascript based framework. Instead, I would recommend Blazor for someone with a Microsoft background or Next.js for those with a React background.</p>
<h3>Cross Platform Desktop Application: Blazor</h3>
<p>This is probably not the first use-case you would think of when thinking about building a desktop application, but in my case, I saw it as the right fit. My challenge was that I needed to build an application on a desktop computer that had direct access to all its resources, such as the file system or database connections. Building this sort of application using strictly a browser and web app wasn't going to cut it for what I needed to do. I then thought I could build a native application to serve as an interface for this, but I'm a full stack web developer by trade... I don't have the time or desire to learn how to build a Windows app... or Swift app... or whatever the hell it is you build a native Linux app in any one of the million distros that are available.</p>
<p>I chose Blazor because I could leverage my web dev experience in creating web UI along with being able to leverage the local resources I need to control the computer. I can access the file system, run executables such as Powershell or bash scripts, and be able to provide a working copy to all three Windows, macOS and Linux developers. I still use the LymeStack API to manage licenses to unlock protected functionality in my app.</p>
<p>As an aside, developing in Blazor has caused me to fall in love with <a href="https://getbootstrap.com/docs/5.3/getting-started/introduction/">Bootstrap</a> again.</p>
<h3>Blogging Software: OrchardCms</h3>
<p>There are many options out there for blogging. I would say that the most popular and widely used software for this is probably <a href="https://www.wordpress.com">WordPress</a>. WordPress is built on PHP and mySQL is probably one of the best blogging packages round. However, this very blog you're reading in instead using a different package called OrchardCms, built on .NET 8. Here's why:</p>
<p><strong>Why not WordPress?</strong>: WordPress is built on PHP and mySQL. Both of these technologies are tried and true, but as I mentioned before, I come from a mostly Microsoft background, so it's not something that's as easy to customize as a .NET based package might be. That and installing PHP, mySQL and the WordPress onto our production servers have a few other implications. 1) Our current production server is running on a fairly low powered VM (IaaS) in an Azure data center. Installing PHP and mySQL would likely increase the montly cost by me having to bump the resources in CPU and Memory to be able to accommodate for that change, which at my level, would double my montly hosting bill seeing as to how my next size tier doubles my RAM from 3.5GB to 7GB, resulting in approximately an additional $150 per month on my hosting bill. 2) Installing PHP, mySQL and WordPress increase the amount of things that I need to keep patched and up to date from a security perspective. Sure, I could consider hosted blogging services, which aren't too expensive, but add to the <a href="./recurring-subscription-services">slow drip of subscription services</a> that widdle away at the bottom line.</p>
<p><strong>Why OrchardCore CMS?</strong>: Because Orchard CMS is built on .NET 8 and supports MS SQL Server, it doesn't require any additional technologies that differ from what we already have in place now and requires very little in added resources to running on our existing infrastructure. The project is feature rich, well maintained, well documented, and there is an active community. It's also free and open source and easily customizable.</p>
<p>There are some other .NET alternatives out there, but after doing a few hours of research, OrchardCore seemed like it was the best fit for me.</p>
<h3>Documentation: Markdown</h3>
<p>All of our documentation in the <a href="https://www.lymestack.com/docs.html">LymeStack Product Manual</a> is authored using standard text-based Markdown files using <a href="https://code.visualstudio.com/">Visual Studio Code</a> and the <a href="https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced">Markdown Preview Enhanced</a> VS Code plug-in, which offers a preview of the Markdown output to the side while editing.  We used this documentation to submit as part of our provisional patent application that we filed with the US Patent and Trademark Office (USPTO). In order to submit the document to the USPTO, we first had to convert it to a combined PDF. For more information about this process, have a look at our <a href="./combined-markdown-assembly">Python Script to Assemble a Combined Markdown File from Multiple Markdown Files</a> blog post.</p>
<p>We also created another c# script, which we'll probably post later, to assemble the same markdown files as HTML documentation that is the online version of the <a href="https://www.lymestack.com/docs.html">LymeStack Product Manual</a>.</p>
<h3>Installer / Setup Builder: Inno Setup</h3>
<p>In order to make LymeTools easier to get up and running on Windows, I use <a href="https://jrsoftware.org/isinfo.php">Inno Setup</a>. Inno is a free installer for Windows programs and has been around since 1997. It is highly customizable. Unfortunately custom workflows use the old Pascal language, but AI platforms like ChatGPT did a good job in helping me implement my workflow customizations. An example of custom workflows in the LymeTools installer configuration is that it checks to see if a user has the .NET 8 SDK installed and if not, direct them to the installer page first.</p>
<h3>Production Cloud Platform: Azure</h3>
<p>Being that I'm a Microsoft guy, I naturally choose Azure to host my production virtual machines. Currently, I only use an Infrastructure as a Service (IaaS) approach as a opposed to a Platform as a Service (PaaS) because I don't want anything cloud proprietary in my code. If I become dissatisfied with my cloud provider, I want to be able to freely switch my cloud provider or go on-premesis. IaaS does have some limitations when it comes to scaling out, but I've nowhere nearly reached that limit, so I have a while to tackle that issue. I'm not an expert in this, but I'm thinking that containerizing a LymeStack instance and using Kubernetes is going to be my cloud-platform-agnostic approach that I will take when I get to that point.</p>
<h3>Local Virtualization Platform: HyperV on Windows or VirtualBox</h3>
<p>For all of my development, I host those resources locally. I built a really nice gaming rig a few months ago, which I don't really use for gaming at all, but instead use it mainly a HyperV host. <a href="https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/">HyperV</a> is built into the professional editions of Windows 10/11 and this HyperV host contains a separate virtual machine (VM) for each of my clients and one for LymeStack. I power them on and off as needed so as to not use all my RAM up at one time and since I'm not working on every client's backlog every day. I usually only need to fire one up if there's a bug report or feature request from a client as most of them are up and running and stable already. This HyperV host also hosts my DevOps Server running Windows Server, which hosts a TeamCity Build Server, Octopus Deployment Server, and the test environment for LymeStack.</p>
<p>If you run macOS or Linux, I recommend <a href="https://www.virtualbox.org/wiki/Downloads">Virtualbox</a>, but admittedly I've had some weird performance issues with system interrupts on my VM's using VirtualBox hosted on my 2017 Intel i7 iMac.</p>
<h3>Credit Card Processing: Stripe</h3>
<p><a href="https://www.stripe.com">Stripe</a> is a popular online payment processing platform that allows businesses to accept and manage payments over the internet. Their pricing is on par with industry standards, typically charging $0.30 + 2.9% per transaction for online payments.</p>
<p>Stripe makes it easy for developers to implement payment processing into their applications, offering extensive documentation and code samples to assist with integration. The platform provides various developer tools, including a command-line interface (CLI) for testing and managing integrations, and sample code designed to help developers get started quickly with Stripe implementations. <a href="https://www.paypal.com">PayPal</a> is another well-known option for online payment processing.</p>
<h3>Domain Names: GoDaddy &amp; SquareSpace (formerly Google Domains)</h3>
<p>There are probably cheaper options out there, but I've been with these providers for years. I've never had to use SquareSpace support, but have to use GoDaddy's support multiple times in the past and most every interaction with them, I've encountered a knowledgeable and well spoken representative. Granted, the last time I contacted them has probably been a few years now, so things could or could not be different from my experience.</p>
<h3>SSL Certificates: PositiveSSL</h3>
<p>SSL certificates are expensive. We found GoDaddy and SquareSpace to be a little more expensive than <a href="https://www.positivessl.com/">PositiveSSL</a>. Here I was able to get a wildcard SSL certificate for $269 per year wheras other providers were closer to $400 per year. You can save on certificates by only securing 1 site and getting a single domain certificate, but every time I decide to do that, I find a need to secure a subdomain causing me to get another single certificate or upgrade to a wildcard certificate.</p>
<h3>FavIcon Generator: Real Favicon Generator</h3>
<p>The little icon that shows in the browser tab was generated by the <a href="https://realfavicongenerator.net">Real Favicon Generator</a> online app. I simply uploaded my logo.png file to the site and it generated me a favicon.ico file as well as a few other iOS / Android specific files.</p>
<h3>SMTP Delivery Service: Postmark</h3>
<p>LymeStack uses <a href="https://postmarkapp.com/">Postmark</a> for delivering messages sent out by our web applications. I have used <a href="https://sendgrid.com/">SendGrid</a> in the past, but we had a few incidents which required some degree of support, which seemed to be non-existent for us when we needed it the most, which led us into the arms of Postmark. Postmark's free tier only allows for 100 emails per month to be sent out via their servers, whereas SendGrid allows for $100 per month, but the bad experience we had me opening my wallet a little sooner. As of the writing of this article, the first paid tier for Postmark is $15 per month for 10,000 emails send quota per month.</p>
<h2>Conclusion</h2>
<p>Navigating the ever-evolving landscape of web development can be daunting, especially for newcomers. However, by leveraging a well-defined tech stack like the one at LymeStack, developers can streamline their processes and enhance their productivity. The choices made—from the robust capabilities of .NET 8 and Angular to the reliability of Microsoft SQL Server—demonstrate a commitment to using the right tools for the job. As technology continues to advance, staying informed and adaptable will be crucial for success in this dynamic field. Embracing these tools not only simplifies development but also fosters innovation, allowing businesses to thrive in an increasingly competitive environment. I hope you found this blog post useful in helping to make some key decisions in choosing your tech-stack.</p>
<p>Thanks for reading!</p>
]]></description>
      <pubDate>Tue, 03 Sep 2024 23:17:54 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/theres-a-lot-to-web-development</guid>
    </item>
    <item>
      <title>Getting Started is the Hardest Part</title>
      <link>https://www.lymestack.com/blog/getting-started-is-the-hardest-part</link>
      <description><![CDATA[<p>When starting a web application from scratch for a small to medium-sized organization, the initial setup can be a daunting and expensive task. This crucial phase often sets the tone for the entire project, and is filled with challenges that can impact the application's success and considerably affect the amount of time it takes to get started with, and actually finish a project.</p>
<p>Sure, buying templates can help, but they usually are only half-stack, meaning that they might help with giving you an attractive mock front-end, but there's still a lot of work to be done in order to integrate those templates with an API back-end.</p>
<h2>Key Setup Components</h2>
<p>The following is a list of things that I think to include when setting up a new application for a client:</p>
<ol>
<li><p><strong>Database Setup Configuration</strong> - Any web application of any decent size has a database of some sort to store and query its data. While there are many databases available on the market, I prefer Microsoft SQL Server. It comes in many editions, its developer tools (SSMS) are mature (I've been using it since year 2000) , and the SQL Express edition is free for production use for databases up to 10GB in size. If you program efficiently, this can go a long way. It's also worth mentioning that it can run on Linux and macOS with the help of Docker. Lastly SQL Server is well supported and the developer community is large.</p>
</li>
<li><p><strong>User Authentication System</strong> - Perhaps I am biased, but this is the absolute worst part of web application development for me. This seemingly trivial task has tied me up for weeks on-end and has added a few grey hairs to my beard. It literally took me over a week to get Entra ID authentication working in my Angular / .NET WebApi web app, navigating through multiple outdated tutorial after tutorial, until I finally found one that finally worked. What a pain! That said, while using local accounts for security was easier and the way of the past, cyber-attacks are becoming more and more sophisticated and this painful exersice has become a necessary evil. Adding support for a single-sign-on (SSO) provider provides added security such as multi-factor authentication (MFA) using SMS messages or even more preferably, an authenticator app. Plus, there's the added benefit of not storing sensitive user passwords in your app database. There are many providers to choose from, but I feel a good choice is <a href="https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account?icid=entraid">Microsoft Azure</a> Entra ID platform for authentication. Like SQL Server, it's widely supported and available from Microsoft.</p>
</li>
<li><p><strong>Error Handling &amp; Reporting Mechanisms</strong> - Errors happen. They're unavoidable. When they do, it's important to get out in front of the issue before the problem becomes more widespread. This also gives you the opportunity to reach out to users, perhaps even before they submit a terse support ticket, turning frustration into delight. When an error happens, first you need to be able to communicate to your users that errors are happening. Almost never just supress the error unless you're absolutely sure that the error has no consequence. In the case that it's a server error (500-level error), you should never present your user with any details about the error, but provide them with a generic message letting them know that someone has been made aware of the issue. Ask the user to try again later or provide a link or email address for further support. In the case of a user or request error (400-level error), you can provide some guided feedback to the user to allow the user to try again or give them explanation for why their attempt didn't work. Lastly, in the case of server errors, you need to have a mechanism that notifies an administrator or developer (via email is good unless the error is email-related) with error details, such as the request URL or stack trace.</p>
</li>
<li><p><strong>Application Navigation Structure</strong> - Typically app navigation menus start out hard-coded, but as apps grow and more user roles / functionality is introduced, this sort of thing will end up in a configuration file or database so that these menus can be more easily dynamic. For example you may want to hide menu options for users that are not in a particular role. A good default example of this would be the Admin menu. Non-administrative users should never see the menu or know it exists. Other times, you may want to attributes to a navigation menu item, such as show a notification count badge next to an item or add a dividing line to appear after it. As these features are added, a navigation menu UI needs to be provided that will allow for editing and sorting of menu items by an administrator.</p>
</li>
<li><p><strong>Mobile and Printer-Friendly UI Design</strong> - Always keeping in mind that your web app could be accessed from a mobile device like a cellphone or tablet. You'll want to implement a responsive design so that content appears gracefully on any device. If your app is heavy in reporting or accounting transactions, it's likely you'll want to support printing your reports to paper or PDF. In order to do this, it's useful to implement CSS print media queries so that you can specify certain elements to print (or not print) when a user invokes the browser's built in print functionality.</p>
</li>
<li><p><strong>Automatic Email Notifications</strong> - It's inevitable that your application will require some way of being able to notify users about certain events that happen in the app. This could be for anything from payment confirmations, welcome messages or any other sort of workflow progress or completion. Often times, the web app's owner will also want those emails to be branded with their company name and logo or want to be able to include some generic information in the footers of their emails such as links to their marketing website or social media platforms. To do this, you would need to build in a mechanism to be able to define the email template message and add tokens to merge data into the emails. Sure, you could probably use a 3rd party email campaign manager, and there is certainly a time and place for those, but often times that can be over-kill for simple messaging. Plus, the price for these online services seem to be ever-increasing over time.</p>
</li>
<li><p><strong>Email / SMTP Setup</strong> - During development it could be a dangerous pratice to send emails from your app running in a local development environment. If you aren't careful and if the right safeguards aren't in place you run the risk of sending test emails to actual live users potentially causing alarm and confusion or even bringing harm to the brand of the web app. In the old days, I would have to cumbersomely comment out code or create configuration settings to disable these messages from being sent out via SMTP. To avoid this risk, I implement a trick where all emails that are sent out via the API get caught in a folder on the server called a <a href="https://stackoverflow.com/questions/58937302/how-to-send-email-locally-in-net-core-without-credentials">SMTP Pickup directory</a>. Only in production should you change a configuration to point to a live SMTP server.</p>
</li>
<li><p><strong>Email Logging</strong> - For auditing and debugging purposes, you'll likely want to be able to keep a log of all emails that go out through the site. In order to allow for admins to self-service, you'll want to provide a UI to be able to see the message via the web app's admin interface. It's likely they will want to export the meta-data too.</p>
</li>
<li><p><strong>Activity Logging</strong> - Again, for audit and debugging purposes, your site's administrators will also want to see when key things happen in their system. Some common examples might include when a user logs in or when a payment gets accepted. You'll want to create a central area where logs can be stored and an interface for site admins to be able to report and filter on certain activity logs. Activity logs can also contain fields for meta data including human readable notes, JSON data, and reference data that can be used to link data to your business entities in custom reports. Again, the data will need to be exportable for further analysis by admins in spreadsheet programs like Excel.</p>
</li>
<li><p><strong>App Health Dashboard</strong> - As your database and file systems grow over time, often times size quotas and limitations don't become an issue until a limit has been exceeded resulting in things suddenly stopping work properly in your app. While this is one of those things that can probably be hedged off for a few months or even years, this <em>will</em> eventually happen, so why not get out in front of it before it does?</p>
</li>
<li><p><strong>User Communication Features</strong> - You will want users to have an easy place to be able to submit feedback and report bugs. For this, create a feedback link that is prominently visible so that it's obvious as to where a user can submit their feedback at any time. Administrators will also want the ability to push communication out to their users by being able to display one-time notices that might warn users about upcoming system maintenance downtime or announce a new feature. Administrators might also want a way to more prominently display notices to their users without users having to refresh their browsers. Such a need might be to announce an office closure due to a holiday, an environmental emergency or system availability issues. This will decrease the number of support inquiries that get submitted as a result of some sort of widespread issue. You'll want to provide your administrators with a way that they can easily do this without having to call a developer in to intervene. Ideally, any notice posted will be allowed to have an expiration date so that they can disappear without an admin having to turn off the notifiation manually.</p>
</li>
<li><p><strong>Centralized Search</strong> - As your core features in your app grow, you will want to provide users with a single location in your application where users can search for features, users, customer names, account numbers, or whatever custom entities you build for your business or client. This search should be a simple text based search that can deliver a variety of results.</p>
</li>
</ol>
<p>Phew, that's a lot! Some things can probably be built as-you-go, but every large app that I have written has contained or needed this level of functionality by the time it got to maturity. I can't really say that I know exactly how long that I think it would take to build such a foundation, I would estimate if it were just myself from scratch, it would take about a month if not more. How long do you think it would take you?</p>
<h2>Common Pitfalls</h2>
<p>While tackling each of the items in the aforementioned laundry list of administrative features, a host of challenges come accompanied with this workload when starting from scratch:</p>
<ol>
<li><p><strong>Tight Timelines:</strong> Often times there is a hurry to get started with core functionality of the application without laying the proper foundation. This leads to a path where lots of shortcuts are taken to "just get something working" and just kicks the can down the road introducing bugs and technical code debt where you're committing yourself to spending time to fix temporary solutions that will be more complete and scale better.</p>
</li>
<li><p><strong>Increased Costs</strong> Initial project setup takes a lot of time. Depending on how you charge your client (hourly vs fixed cost), this time it takes to develop the foundation and administrative features for your app could be cost prohibitive for a customer or cause you to lose the contract to a competitor who might not be starting from scratch.</p>
</li>
<li><p><strong>Unpolished Appearance:</strong> The focus on functionality can result in a less-than-ideal user interface in the early stages. If multiple developers are involved, you also run the probable risk that portions of the application built by different developers could look pretty different from one another.</p>
</li>
<li><p><strong>Delayed Core Functionality:</strong> Most, if not all of these tasks must be completed before developers can focus on the application's primary features. This means weeks or even months of project infrastructure development will set you back that much time in getting the actual custom application developed.</p>
</li>
<li><p><strong>Security Oversights:</strong> In the rush to get the application running, security measures are sometimes treated as an afterthought rather than a priority.</p>
</li>
</ol>
<p>All of the challenges and risks associated with starting a project from scratch can significantly delay the delivery of an application, increase costs for your customers and erode user confidence and trust in your web application.</p>
<h2>Strategies for Success</h2>
<p>When starting a web application from scratch, you should employ the following steps to mitigate risks and tighten timelines:</p>
<ol>
<li><p><strong>Prioritize Planning:</strong> Allocate sufficient time for the initial planning phase.</p>
</li>
<li><p><strong>Use Proven Frameworks:</strong> Leverage existing frameworks and libraries to accelerate development and improve reliability.</p>
</li>
<li><p><strong>Implement Agile Methodologies:</strong> Adopt iterative development processes to catch and fix issues early.</p>
</li>
<li><p><strong>Focus on Security:</strong> Make security a priority from day one, integrating it into every aspect of the application.</p>
</li>
<li><p><strong>Balanced Approach:</strong> Strike a balance between functionality, aesthetics, and performance in the early stages.</p>
</li>
</ol>
<p>By understanding these challenges and implementing strategies to address them, development teams can lay a solid foundation for their web applications, setting the stage for long-term success.</p>
<h2>Or.... Check out LymeStack</h2>
<p>LymeStack is a web applicaton template and toolset I developed for myself to help me with laying all of the foundational groundwork that are described here in this article. LymeTools are the developer tools I created to help me to instantiate new instances of apps for me and my clients. It also helps me to eliminate repetitive CRUD coding when it comes to building core app functionality. This stable app template is a crucial part to being able to <a href="./vacation-proof-your-application">allow myself to take a vacation as a soloprenuer</a>.</p>
<h2>Conclusion</h2>
<p>As a soloprenuer, I can't afford to start from scratch every time I take on a new client. Smaller businesses typically have tighter budgets, so if I were to have to start from scratch every single time I started a project, the cost and time to set up could make me less competitive and the client might run out of money before we even get started with implementing their core functionality. I built LymeStack as a few sloppy console apps that would automate much of my repetive tasks. I figured that if they were of use to me, they could be of some use to someone else. Almost 8 months ago, I tried getting an instance of the app up and running on a colleague's local computer and the process still took us several days of back-and-forth to get things working. That's when the LymeStack officially started by consolidating all of these sloppy tools into a more refined developer experience. If this intrigues you, please check our our <a href="https://www.lymestack.com">Getting Started Guide</a> to activate a free trial of LymeStack and get an application running on your local dev environment in an hour or less (YRMV).</p>
]]></description>
      <pubDate>Tue, 03 Sep 2024 23:21:44 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/getting-started-is-the-hardest-part</guid>
    </item>
    <item>
      <title>Mic Check... Check, Check One-Two</title>
      <link>https://www.lymestack.com/blog/introduction</link>
      <description><![CDATA[<p>Welcome to the LymeStack blog! 🎙️</p>
<p>Hello! I'm Mike and I've been in this custom web application development game for longer than I'd like to admit. Back in 1996, I discovered the web as a student at the University of Maryland College Park, in the armpit of all dorms, the notorious Denton 3. It was an odd mix of people - from the obnoxious New York / New Jersey frat / sorority scene to the hardcore computer nerds. As a young man without a tribe, I found myself in the middle of this eclectic mix. Denton 3 (3rd floor) was a highly sought after for reasons unknown to me by the Jersey Shore crowd - perhaps it was some sort of legacy destination. The nerds motivation was clear because Denton Hall was one of the first dorms to have ethernet Internet connections newly installed in all rooms. I was lucky(?) enough to have been randomly assigned to this rare purgatory. During this period and thanks to the newly installed technology, I discovered the web, which was very early in its existence. I was fascinated with this new online world back when it was still very quirky and cool and not the huge mall that it is today. It was really lucky timing to get started with computers and programming.</p>
<p>I wanted to learn more about the web and I found and printed out the HTML 1.0 specifiction document, which was pretty short and fundamentally simple. There was only HTML... no CSS, no Javascript, so it was a lot easier to wrap my head around concepts because there just simply weren't too many concepts to learn. Maybe that's why I like <a href="https://www.markdownguide.com">Markdown</a> so much. It very much resembles what you could do with HTML 1.0. Not long after studying the spec, I built my very own personal website - a "me" page as my good friend put it. :)  (I actually found a trace of it on <a href="https://web.archive.org/web/19970305082554/http://www.wam.umd.edu/%7Emjoseph/">The Way Back Machine</a>. I used it as a study guide by transcribing my class notes into HTML. For some reason typing my notes was the only way I could seem to retain the information.</p>
<p>While HTML at that time was conceptually very simple, it could quickly become very complicated in its implementation. Since early versions of CSS really only would apply to typography, layout on web pages were typically built using clever and complex implemtations of HTML tables, transparent spacer images or IFRAMEs, which could be very awkward to work with. There were also no developer tools built into the browsers, so debugging CSS could prove to be a newly created hell by the architects of the web. It was a big mess, but it provided me with the opportunity to see the web evolve from what it was to what it is today, allowing me to see each piece come together bit by bit instead of being inundated with so many moving and working parts that a modern application uses. It allowed me to become an early-expert mostly because the web was so new, so nobody really knew what they were doing!</p>
<p>I can only imagine how overwhelming it must be for a new student to enter into the web development world as it is today. <a href="./theres-a-lot-to-web-development">There's a lot to it</a> and this blog will hopefully help with someone who is new in finding their own way in this business by sharing our experiences in the topics of web app development. This blog will also help people who aren't as new to it as well by touching on more advanced topics and subjects such as <a href="./getting-started-is-the-hardest-part">setting up new web applications</a>, database design, and DevOps. We also plan to write about our tools here at LymeStack, how we built them to solve our own problems, and how they can help to make the development experience (hopefully) more enjoyable for you.</p>
<p>We have a ton of ideas for content in the coming months, so stay tuned. Hopefully the information you find here will prove useful for some of you. If you have any feedback, questions or requests for content, feel free to <a href="https://www.lymestack.com/#contact">contact us</a>. Thanks for reading!</p>
]]></description>
      <pubDate>Wed, 28 Aug 2024 17:39:55 GMT</pubDate>
      <guid isPermaLink="true">https://www.lymestack.com/blog/introduction</guid>
    </item>
  </channel>
</rss>