<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Philip Perry</title>
    <description>The latest articles on Forem by Philip Perry (@programmingdecoded).</description>
    <link>https://forem.com/programmingdecoded</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F305798%2F76e99e78-97d7-47f9-81fd-11f5f198f94c.png</url>
      <title>Forem: Philip Perry</title>
      <link>https://forem.com/programmingdecoded</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/programmingdecoded"/>
    <language>en</language>
    <item>
      <title>Getting In-App Purchases working for an Android App created with React Expo</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Sat, 09 Aug 2025 00:17:02 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/getting-in-app-purchases-working-for-an-android-app-created-with-react-expo-4phh</link>
      <guid>https://forem.com/programmingdecoded/getting-in-app-purchases-working-for-an-android-app-created-with-react-expo-4phh</guid>
      <description>&lt;p&gt;When I set out to add in-app purchases (IAP) to &lt;a href="https://play.google.com/store/apps/details?id=com.artisanphil.myaudiostory" rel="noopener noreferrer"&gt;Interactive Audio Stories&lt;/a&gt;, I imagined this would be very simple as I’m not the first to do it. But the reality was a marathon of backend and frontend changes, endless debugging, and a lot of learning. Here’s how it unfolded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backstory
&lt;/h2&gt;

&lt;p&gt;While users can read stories for free, all these AI credits cost me money so I decided to add a paid feature which allows users to generate their own interactive stories. They can login with a Google user and we keep track of how many stories they can create with a &lt;code&gt;count&lt;/code&gt; field in the Firebase users collection. So initially &lt;code&gt;count&lt;/code&gt; is set to zero, but when they make an in-app purchase it adds 1 to the existing value which will allow them to create one story (or more if they made multiple purchases before creating a story).&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Payment Library
&lt;/h2&gt;

&lt;p&gt;Initially, I went with RevenueCat, but I found their implementation an overkill for a simple app like mine and more geared towards subscriptions. I also couldn’t get it to work as intended. Then I tried Adapty. What I didn’t understand at first it that Adapty does not support consumables. A consumable is a purchase that unlocks content that can be "consumed", e.g. for my app that would be "consuming" the generation of one story. So making an in-app purchase doesn't unlock the feature forever, but just for one time. &lt;/p&gt;

&lt;p&gt;Finally, I came across &lt;a href="https://github.com/hyochan/expo-iap" rel="noopener noreferrer"&gt;https://github.com/hyochan/expo-iap&lt;/a&gt; which does exactly what I need. One needs to implement validation in the backend, but the app needs to make a call to the backend anyways in order to increment the count field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Validating Receipts with Google Play
&lt;/h3&gt;

&lt;p&gt;A key part of secure IAP is verifying the purchase server-side. This Go snippet shows how to call the Google Play API to validate a purchase token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;url := fmt.Sprintf("https://androidpublisher.googleapis.com/androidpublisher/v3/applications/%s/purchases/products/%s/tokens/%s",
    req.PackageName, req.ProductId, req.ProductToken)
reqGoogle, err := http.NewRequest("GET", url, nil)
reqGoogle.Header.Set("Authorization", "Bearer "+accessToken)
resp, err := client.Do(reqGoogle)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Annoyingly, the first draft which I generated with LLM passed in the access token as a query string instead of setting an authorization header and it took me a while to realise that. I don't know how vibe coders create these type of apps...&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the access token
&lt;/h3&gt;

&lt;p&gt;In order to make the above request for verifying the purchase, one first needs to make a request to get the access token. To access the Google Play API, one needs to load service account credentials which is a json file that one needs to create and download from Google cloud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func getGoogleAccessToken() (string, error) {
    ctx := context.Background()
    credPath := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
    if credPath == "" {
        return "", fmt.Errorf("GOOGLE_APPLICATION_CREDENTIALS env var not set")
    }
    log.Printf("[Receipt] Loading Google credentials from: %s", credPath)
    jsonData := readFileOrPanic(credPath)
    // Log client_id from the service account JSON
    var credMeta struct {
        ClientID string `json:"client_id"`
    }
    if err := json.Unmarshal(jsonData, &amp;amp;credMeta); err == nil {
        log.Printf("[Receipt] Service Account client_id: %s", credMeta.ClientID)
    } else {
        log.Printf("[Receipt] Could not parse client_id from service account JSON: %v", err)
    }
    creds, err := google.CredentialsFromJSON(ctx, jsonData, "https://www.googleapis.com/auth/androidpublisher")
    if err != nil {
        return "", err
    }
    log.Printf("[Receipt] Successfully loaded Google credentials")

    token, err := creds.TokenSource.Token()
    if err != nil {
        return "", err
    }
    return token.AccessToken, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;This is how we handle the button click &lt;code&gt;handlePurchase&lt;/code&gt;. One thing worth noting is that for Android we pass in &lt;code&gt;skus&lt;/code&gt; while for iOS it's &lt;code&gt;sku&lt;/code&gt;. I decided to pass in both, even though we don't support iOS at  the moment...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handlePurchase = async () =&amp;gt; {
    let productId = 'my_product';

    try {

      await requestPurchase({
        request: {
          sku: productId,
          skus: [productId],
        },
        type: 'inapp',
      });
    } catch (error) {
      console.log("products loaded", products),
        console.error('The purchase failed:', error);
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part of the frontend code that makes a request to the backend verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    if (currentPurchase) {
      const completePurchase = async () =&amp;gt; {
        console.log('Current purchase:', currentPurchase);
        try {
          // Parse the purchase receipt
          let verifiedPurchase = JSON.parse(currentPurchase?.transactionReceipt);
          const packageName = "com.artisanphil.myaudiostory";
          const productToken = verifiedPurchase.purchaseToken;
          const productId = verifiedPurchase.productId;

          // Call backend for receipt validation
          const res = await fetch(`${API_BASE}/receipt/validate`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              packageName,
              productToken,
              productId,
              userId: userInfo.id,
            }),
          });
          const data = await res.json();
          if (!res.ok || !data.valid) {
            Alert.alert('Purchase Invalid', 'Your purchase could not be validated. Please contact support.');
            return;
          }

          // Finish the transaction
          await finishTransaction({
            purchase: currentPurchase,
            isConsumable: true, // Set true for consumable products
          });

          // Grant the purchase to user
          console.log('Purchase validated and completed successfully!');
          await refreshUserData(userInfo, "storage", setUserInfo);
        } catch (error) {
          console.error('Failed to complete purchase:', error);
        }
      };

      completePurchase();
    }
  }, [currentPurchase]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we pass in &lt;code&gt;isConsumable: true&lt;/code&gt; when finishing the transaction.&lt;/p&gt;

&lt;p&gt;There is a bit more to it, but best to consult the official documentation at &lt;a href="https://expo-iap.hyo.dev/docs/api/use-iap/" rel="noopener noreferrer"&gt;https://expo-iap.hyo.dev/docs/api/use-iap/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Programming isn’t always smooth sailing, even for seasoned developers, and that’s part of what makes it interesting. Since I couldn’t find any existing article for this particular use case, I wanted to share my own experience in the hope it saves you a few bumps along the way.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Coding is Messy – Or Why Developers Are Still Indispensable</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Sat, 19 Jul 2025 06:34:50 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/coding-is-messy-or-why-developers-are-still-indispensable-2gna</link>
      <guid>https://forem.com/programmingdecoded/coding-is-messy-or-why-developers-are-still-indispensable-2gna</guid>
      <description>&lt;p&gt;With all the talk of AI replacing developers, it is easily forgotten that developing software is essentially still a human activity. AI certainly can help speed up the process, but there are so many complexities involved that require human intervention. Talking with the stakeholders is certainly something that a vibe coder can do, but I’m referring to the code itself. While AI might be able to quickly scaffold an initial program, when it comes to making changes, human developers still possess the critical understanding of the requirements, architectural implications, and long-term maintainability to effectively adapt and evolve the codebase. &lt;/p&gt;

&lt;p&gt;One skill needed by software developers that is often overlooked is perseverance. This is especially true when it comes to working with libraries or external systems. For example, I’ve recently been trying to get billing to work for an Android app (a small side-project). While the libraries keep getting updated, I found that the maintainers oftentimes forget updating the documentation. Recently I found that the code that I needed to use was on the migration page, but nobody had updated the documentation page itself. Sometimes AI can be good at returning the actual implementation one is looking for, but more often than not, it will return a hallucinated function name. In these cases, vibe coding will lead nowhere… &lt;/p&gt;

&lt;p&gt;Another area where human software developers are needed is debugging an issue. In order to be able to reproduce an issue, one needs to painstakingly step through the code, view the logs and examine data at each step. Again, an AI agent can help with these steps, but it requires human guidance as there can be many points of failures, some which might be outside of the codebase itself. &lt;/p&gt;

&lt;p&gt;From the nuanced conversations with stakeholders to the messy reality of debugging a tricky issue or wrestling with poorly documented legacy code, AI simply isn't equipped to handle the full spectrum of challenges.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Versioning in Go Huma</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Fri, 10 Jan 2025 17:09:42 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/versioning-in-go-huma-5g0p</link>
      <guid>https://forem.com/programmingdecoded/versioning-in-go-huma-5g0p</guid>
      <description>&lt;p&gt;We want to have a separate documentation for each version in Go Huma, e.g. /v1/docs, /v2/docs, etc.&lt;/p&gt;

&lt;p&gt;This can be done by setting the docs path like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config.DocsPath = "/{version}/docs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use middleware to get the version from the path in the request and load the description used in the docs depending on the version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config := huma.DefaultConfig("API V"+versionNumberString, versionNumberString+".0.0")
                overviewFilePath := filepath.Join("/app/docs", fmt.Sprintf("v%s", versionNumberString), "overview.md")
                overview, err := ioutil.ReadFile(overviewFilePath)
                if err != nil {
                    log.Fatalf("Error reading file: %v", err)
                }

                config.Info.Description = string(overview)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can import all routes that we want to display in the docs based on the version. This will also load them so that they can be used as actual endpoints. &lt;/p&gt;

&lt;p&gt;This is the complete code for routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router := chi.NewMux()
    router.Use(func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            urlPathParts := strings.Split(r.URL.Path, "/")
            versions := []string{"v1", "v2", "v3"}
            if helpers.Contains(versions, urlPathParts[1]) {
                versionPath := urlPathParts[1]
                if versionPath == "" {
                    http.Error(w, "version does not exist", http.StatusInternalServerError)
                }
                versionNumberString := strings.TrimPrefix(versionPath, "v")
                versionNumber, _ := strconv.Atoi(versionNumberString)
                config := huma.DefaultConfig("API V"+versionNumberString, versionNumberString+".0.0")
                overviewFilePath := fmt.Sprintf("docs/v%s/overview.md", versionNumberString)
                overview, err := ioutil.ReadFile(overviewFilePath)
                if err != nil {
                    log.Fatalf("Error reading file: %v", err)
                }

                config.Info.Description = string(overview)
                api := humachi.New(router, config)

                switch versionNumber {
                case 1:
                    api = v1handlers.AddV1Middleware(api)
                    v1handlers.AddV1Routes(api)
                case 2:
                    api = v2handlers.AddV2Middleware(api)
                    v2handlers.AddV2Routes(api)
                default:
                    api = v3handlers.AddV3Middleware(api)
                    router = v3handlers.AddV3ErrorResponses(router)
                    v3handlers.AddV3Routes(api)
                }
            }

            next.ServeHTTP(w, r)
        })
    })

    config := huma.DefaultConfig("API V3", "3.0.0")
    config.DocsPath = "/{version}/docs"

    humachi.New(router, config)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>go</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Adding filter query parameters in Go Huma</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Fri, 29 Nov 2024 19:25:33 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/adding-filter-query-parameters-in-go-huma-249m</link>
      <guid>https://forem.com/programmingdecoded/adding-filter-query-parameters-in-go-huma-249m</guid>
      <description>&lt;p&gt;&lt;del&gt;From what I have been able to find out, &lt;a href="https://huma.rocks/" rel="noopener noreferrer"&gt;Huma&lt;/a&gt; unfortunately doesn't support array query filters like this: &lt;code&gt;filters[]=filter1&amp;amp;filters[]=filter2&lt;/code&gt; (neither leaving the brackets out, e.g. &lt;code&gt;filter=filter1&amp;amp;filter=filter2&lt;/code&gt;). I came across this Github issue that gives an example of separating the filters by comma &lt;a href="https://github.com/danielgtaylor/huma/issues/325" rel="noopener noreferrer"&gt;https://github.com/danielgtaylor/huma/issues/325&lt;/a&gt;, so that's what we ended up doing: &lt;code&gt;filters=postcode:eq:RM7%28EX,created:gt:2024-01-01&lt;/code&gt;&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;I found a way to use repeated query parameters, so that I don't need to separate them by comma after all. It can be done using the &lt;code&gt;explode&lt;/code&gt; option in the struct tag. So below you will find the updated implementation (I deleted the old one).&lt;/p&gt;

&lt;p&gt;The query string will look like this: &lt;code&gt;filters=postcode:eq:RM7%28EX&amp;amp;filters=created:gt:2024-01-01&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Documenting filters
&lt;/h2&gt;

&lt;p&gt;Unlike the body parameters, which one can simply specify as structs and then they get both validated and generated in the documentation, the documentation and validation for filters has to be done separately. &lt;/p&gt;

&lt;p&gt;The documentation can simply be added under the description attribute of the Huma.Param object (under Operation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parameters: []*huma.Param{{
            Name: "filters",
            In:   "query",
            Description: "Filter properties by various fields.\n\n" +
                "Format: field:operator:value\n\n" +
                "Supported fields:\n" +
                "- postcode (operator: eq)\n" +
                "- created (operators: gt, lt, gte, lte)\n",
            Schema: &amp;amp;huma.Schema{
                Type: "string",
                Items: &amp;amp;huma.Schema{
                    Type:    "string",
                    Pattern: "^[a-zA-Z_]+:(eq|neq|gt|lt|gte|lte):[a-zA-Z0-9-:.]+$",
                },
                Examples: []any{
                    "postcode:eq:RM7 8EX",
                    "created:gt:2024-01-01",
                },
            },
            Required: false,
        }},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdka8gh900t0bw21zmqb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdka8gh900t0bw21zmqb4.png" alt="Image description" width="770" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now define our &lt;code&gt;PropertyFilterParams&lt;/code&gt; struct for validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type FilterParam struct {
    Field    string
    Operator string
    Value    interface{}
}

type PortfolioFilterParams struct {
    Items []FilterParam
}

func (input *PortfolioQueryParams) Resolve(ctx huma.Context) []error {
    equalityFields := []string{"property_id", "external_id", "address", "action_text"}
    greaterSmallerFields := []string{"households", "occupants"}
    dateFields := []string{}

    var errs []error
    for idx, f := range input.Filters {
        _, err := parseAndValidateFilterItem(f, equalityFields, greaterSmallerFields, dateFields)
        if err != nil {
            errs = append(errs, &amp;amp;huma.ErrorDetail{
                Location: fmt.Sprintf("query.filters[%d]", idx),
                Message:  err.Error(),
                Value:    f,
            })
        }
    }

    return errs

}

func (s *PortfolioFilterParams) Schema(registry huma.Registry) *huma.Schema {
    return &amp;amp;huma.Schema{
        Type: huma.TypeString,
    }
}

func parseAndValidateFilterItem(item string, equalityFields []string, greaterSmallerFields []string, dateFields []string) (FilterParam, error) {
    parts := strings.SplitN(item, ":", 3)

    field := parts[0]
    operator := parts[1]
    value := parts[2]

    if contains(equalityFields, field) {
        if operator != "eq" &amp;amp;&amp;amp; operator != "neq" {
            return FilterParam{}, fmt.Errorf("Unsupported operator %s for field %s. Only 'eq' and 'neq' are supported.", operator, field)
        }
    } else if contains(greaterSmallerFields, field) {
        if !validation.IsValidCompareGreaterSmallerOperator(operator) {
            return FilterParam{}, fmt.Errorf("Unsupported operator %s for field %s. Supported operators: eq, neq, gt, lt, gte, lte.", operator, field)
        }
    } else if contains(dateFields, field) {
        if !validation.IsValidCompareGreaterSmallerOperator(operator) {
            return FilterParam{}, fmt.Errorf("Unsupported operator %s for field %s. Supported operators: eq, neq, gt, lt, gte, lte.", operator, field)
        }
        if !validation.IsValidDate(value) {
            return FilterParam{}, fmt.Errorf("Invalid date format: %s. Expected: YYYY-MM-DD", value)
        }
    } else {
        return FilterParam{}, fmt.Errorf("Unsupported filter field: %s", field)
    }

    return FilterParam{Field: field, Operator: operator, Value: value}, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is how the &lt;code&gt;PortfolioQueryParams&lt;/code&gt; struct looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type PortfolioQueryParams struct {
    PaginationParams
    Filters []string `query:"filters,explode" doc:"Filter portfolio by various fields"`
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how adding &lt;code&gt;PortfolioQueryParams&lt;/code&gt; to the route looks like (note that the Operation code itself, including the filter description, is under &lt;code&gt;getAllPropertyOperation&lt;/code&gt; - I didn't paste the complete code for that, but hopefully you get the gist of it). If validation fails, it will throw a 422 response. I also added how we can loop through the filter values that got passed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;huma.Register(api, getAllPropertyOperation(schema, "get-properties", "/properties", []string{"Properties"}),
        func(ctx context.Context, input *struct {
            models.Headers
            models.PortfolioQueryParams
        }) (*models.MultiplePropertyOutput, error) {

            for _, filter := range input.Filters{
                fmt.Println(filter)
            }

            return mockMultiplePropertyResponse(), err
        })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this helps someone. Let me know in the comments, if you found a better solution. &lt;/p&gt;

</description>
      <category>go</category>
      <category>api</category>
    </item>
    <item>
      <title>Why we chose the Go Huma framework to develop our API endpoints</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Thu, 07 Nov 2024 20:58:58 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/why-we-chose-the-go-huma-framework-to-develop-our-api-endpoints-3h9l</link>
      <guid>https://forem.com/programmingdecoded/why-we-chose-the-go-huma-framework-to-develop-our-api-endpoints-3h9l</guid>
      <description>&lt;p&gt;At the company where I work as a software engineer, we are in the process of developing an API that communicates with our micro services and will be used by our own products as well as being an API that our clients can use. Our overall deciding factor for choosing Golang for this was speed. Apart from fast execution time, Go also offers low memory consumption and efficient concurrency. &lt;/p&gt;

&lt;p&gt;When it came to the first step of specifying the endpoints, we were looking for a solution to do so in code as we wanted to avoid a discrepancy between code and documentation. Our CEO had used the same approach using Python FastApi, so we googled for a FastApi solution for Go and came across &lt;a href="https://huma.rocks/" rel="noopener noreferrer"&gt;Huma&lt;/a&gt;. With Huma one can automatically generate OpenAPI documentation from code and it generates a nice-looking documentation using stoplight elements. It generates the JSON schema from Go types and uses static typing for path/query/header params, bodies, response headers, etc. It does input model validation &amp;amp; error handling automatically based on the json schema.&lt;/p&gt;

&lt;p&gt;We’ve found the framework fairly flexible and it allows for instance to use one own's router, although we just stuck with Chi which it uses as the default one. There are some downsides, for instance it doesn’t seem to support array query parameters, so we’re separating filters by comma. But that hasn’t been a deal breaker. &lt;/p&gt;

&lt;p&gt;I plan to write more about my experience and learnings with Go Huma in future posts, but so far I have found it fit for purpose. &lt;/p&gt;

</description>
      <category>go</category>
      <category>api</category>
      <category>documentation</category>
      <category>openapi</category>
    </item>
    <item>
      <title>Follow me on my journey learning Go</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Tue, 05 Nov 2024 21:29:18 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/follow-me-on-my-journey-learning-go-a40</link>
      <guid>https://forem.com/programmingdecoded/follow-me-on-my-journey-learning-go-a40</guid>
      <description>&lt;p&gt;I'm developing a little game using Go in the backend and React with TypeScript for the frontend. In my day job I mostly work with PHP using the Laravel framework, so Go is a new programming language for me and I find the best way to learn is by doing. So follow me on my journey on building this project. I'm already half way done, but I'll share any future PRs with you.&lt;/p&gt;

&lt;p&gt;The game is called Suspect Recall. You can view what I have so far here: &lt;a href="https://www.suspectrecall.com" rel="noopener noreferrer"&gt;https://www.suspectrecall.com&lt;/a&gt; First you see a suspect for a few seconds and then you have to remember which attributes that suspect had. I'll be improving the design a bit later, although that's not the focus of this project.&lt;/p&gt;

&lt;p&gt;Next on the todos is fetching a random suspect. I opened a PR for that part of the code: &lt;a href="https://github.com/artisanphil/suspect_recall/pull/4" rel="noopener noreferrer"&gt;https://github.com/artisanphil/suspect_recall/pull/4&lt;/a&gt; Code reviews are welcome! I also plan to save the answers so that I can get an idea of how many people try out the website and how many mistakes they make.&lt;/p&gt;

&lt;h2&gt;
  
  
  An overview of the code structure
&lt;/h2&gt;

&lt;p&gt;Originally I used to have two folders, backend and frontend, but I found it actually works best to have the backend code in the root and the frontend folder inside the backend folder.&lt;br&gt;
We don't need to deploy the frontend code, just the code that gets created from the build. Find out in the Readme file how to run the code for local development.&lt;/p&gt;

&lt;p&gt;If you just downloaded the project, you will need to run &lt;code&gt;npm install&lt;/code&gt; in the frontend folder to pull the dependencies into the folder 'node_modules'.&lt;br&gt;
Create a .env file in the frontend folder and add &lt;code&gt;REACT_APP_MODE=development&lt;/code&gt;. This is so that when running frontend code with live reload, it will call the api endpoints which run on another port. Then run &lt;code&gt;npm run start&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can now go to localhost:3000 and view the frontend. As you will see, the api endpoints won't work, so let's go to the root folder and run &lt;code&gt;go run .&lt;/code&gt;.  Note that we needed to allow cross-domain requests when running locally, as it's running on another port (port 8080, frontend is on 3000).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c := cors.New(cors.Options{
        AllowedOrigins:   []string{"http://localhost:3000"},
        AllowedHeaders:   []string{"Origin", "Content-Type", "Accept"},
        AllowCredentials: true,
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When running on production, it will all run on the same port as we just run the backend code after having built the frontend code with &lt;code&gt;npm run build&lt;/code&gt; which creates the static files. BTW, I deployed the code to Google App Engine.&lt;/p&gt;

&lt;p&gt;Please review this PR where I add a new api for dynamically fetching a suspect (which is currently hard-coded) and calling that endpoint in the frontend: &lt;a href="https://github.com/artisanphil/suspect_recall/pull/4" rel="noopener noreferrer"&gt;https://github.com/artisanphil/suspect_recall/pull/4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks in advance for any comments on how the code can be improved and I'll do my best to answer any questions that you might have.&lt;/p&gt;

&lt;p&gt;In order to view future progress, please watch this repository: &lt;a href="https://github.com/artisanphil/suspect_recall" rel="noopener noreferrer"&gt;https://github.com/artisanphil/suspect_recall&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>learning</category>
      <category>beginners</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Fitting a square peg into a round hole - a painful lesson</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Tue, 20 Feb 2024 08:22:05 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/fitting-a-square-peg-into-a-round-hole-a-painful-lesson-3jm5</link>
      <guid>https://forem.com/programmingdecoded/fitting-a-square-peg-into-a-round-hole-a-painful-lesson-3jm5</guid>
      <description>&lt;p&gt;Software development encompasses far more than mere syntax mastery. The real challenges lie in communication and strategic planning. Let me share a story that illustrates this point vividly.&lt;/p&gt;

&lt;p&gt;We recently encountered a requirement that lacked a straightforward solution. Rather than immediately pushing back, we embarked on a journey to meet it head-on.&lt;/p&gt;

&lt;p&gt;The requirement was seemingly simple: retrieve a child item of a parent where no direct 1-to-1 relationship existed. Initially, our solution involved returning the latest created child item. However, the first round of QA exposed that the logic wasn't that simple. We delved deeper, crafting more intricate solutions, only to face scrutiny during the subsequent QA round.&lt;/p&gt;

&lt;p&gt;The new QA reviewer rightly questioned the convoluted logic, foreseeing its potential pitfalls and the inevitable maintenance challenges it posed. After consultation with the product team, we collectively agreed that the necessity of returning this child item was questionable. &lt;/p&gt;

&lt;p&gt;If we had understood this earlier, we could have saved a lot of time. At this stage the logic was already used in several places, so it will take a few days to rip it out.  &lt;/p&gt;

&lt;p&gt;Reflecting on this experience, I realise several key lessons. Firstly, I must dedicate more time to understanding data sources thoroughly. Even seemingly straightforward scenarios can conceal hidden complexities, especially in cases lacking direct relationships.&lt;/p&gt;

&lt;p&gt;Secondly, early engagement with the product team is crucial when complexities arise. Transparency about potential challenges facilitates collaborative exploration of alternative solutions. While our initial logic might have sufficed for most cases, it wasn't a sustainable solution.&lt;/p&gt;

&lt;p&gt;Despite holding the title of senior engineer, this experience felt like an initiation rite, revealing the true breadth of responsibilities inherent in my role. Moving forward, I commit to thorough scenario analysis, even if it delays project initiation. By preemptively addressing potential issues, we can develop simpler yet robust solutions that deliver tangible value without succumbing to over-engineering.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>softwaredevelopment</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Integrating SSO with Laravel Auth Provider</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Mon, 29 Jan 2024 19:30:09 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/integrating-sso-with-laravel-auth-provider-185l</link>
      <guid>https://forem.com/programmingdecoded/integrating-sso-with-laravel-auth-provider-185l</guid>
      <description>&lt;p&gt;At my company we have our own SSO server (based on Laravel passport) and we use our sdk (Laravel package) that provides the middleware and other functionality to communicate with the SSO server and we add to all our microservices. &lt;/p&gt;

&lt;p&gt;We decided to add the functionality of the SDK so that our SSO user data gets passed into the Auth provider. The Auth facade allows one to do things like fetching the logged-in user with &lt;code&gt;Auth::user()&lt;/code&gt;. Thankfully Laravel allows one to extend the &lt;a href="https://laravel.com/docs/10.x/authentication#the-user-provider-contract"&gt;user provider&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the methods that can be overwritten is &lt;code&gt;retrieveById&lt;/code&gt;. Our code to fill the Auth user looks something like this (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
namespace Company\SSO\Auth\UserProviders;

use Illuminate\Contracts\Auth\Authenticatable
use Illuminate\Contracts\Auth\UserProvider;

class SSOUserProvider implements UserProvider
{
  public function retrieveById($identifier): ?Authenticatable
  {
    $user = SSO::webUser(); //this fetches the web user from  our SSO server

   if(!$user) { 
    return null;
   }

  /**
  * LaravelUser is a class that we created that implements the
  * Authenticatable contract 
  */
  return new LaravelUser(
    $user-&amp;gt;id,
    $user-&amp;gt;name,
    $user-&amp;gt;email,
    $user-&amp;gt;emailVerifiedAt,
    $user-&amp;gt;isAdmin,
    $user-&amp;gt;createdAt,
    $user-&amp;gt;updatedAt,
    $user-&amp;gt;activeGroup 
  );
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom user provider needs to be added to the auth.php config and resolved in the boot method of the ServiceProvider class. You can read about this here: &lt;a href="https://laravel.com/docs/10.x/authentication#adding-custom-user-providers"&gt;Adding custom user providers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We actually went a step further and also added custom guards by using &lt;code&gt;Auth::extend()&lt;/code&gt; in the boot method of the ServiceProvider. For that we pretty much followed what is described here: &lt;a href="https://laravel.com/docs/10.x/authentication#adding-custom-guards"&gt;Adding custom guards&lt;/a&gt;&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>laravel</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Scribe not extracting docblocks</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Sat, 30 Sep 2023 18:31:36 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/scribe-not-extracting-docblocks-3ihh</link>
      <guid>https://forem.com/programmingdecoded/scribe-not-extracting-docblocks-3ihh</guid>
      <description>&lt;p&gt;Scribe can use PHP docblocks for generating documentation. On a newer Laravel project this no longer worked and initially I thought it might be related to a newer version of Scribe as we didn't have that version in other projects where we were using an older version of Scribe. But a downgrade was causing conflicts, so I couldn't evaluate this. However, it seemed odd that apart from one Github issue, nobody was complaining. So that pointed towards a configuration issue, but I couldn't see anything different inside the scribe config file.&lt;/p&gt;

&lt;p&gt;Finally, after debugging the GetFromDocBlocks strategy class, my colleague came across the fact that the getDocComment method is a PHP reflection method. This led to a Google search where we found this Stackoverflow post: &lt;a href="https://stackoverflow.com/questions/42087433/reflectionmethod-getdoccomment-doesnt-seem-to-work-on-php-5-5"&gt;https://stackoverflow.com/questions/42087433/reflectionmethod-getdoccomment-doesnt-seem-to-work-on-php-5-5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It turns out, that for this project we were using OpCache, but had the configuration &lt;code&gt;opcache.save_comments&lt;/code&gt; set to false. After changing the setting &lt;code&gt;opcache.save_comments=1&lt;/code&gt; and rebuilding the Docker container, Scribe worked as expected! &lt;/p&gt;

&lt;p&gt;This one was slightly tricky to debug and credit goes to my colleague who patiently debugged the issue with me.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Evaluating Nuxt as a Laravel Fullstack developer</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Sun, 17 Sep 2023 16:36:30 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/evaluating-nuxt-as-a-laravel-fullstack-developer-2jjk</link>
      <guid>https://forem.com/programmingdecoded/evaluating-nuxt-as-a-laravel-fullstack-developer-2jjk</guid>
      <description>&lt;p&gt;As a full-stack developer, choosing the right technology stack for your web projects is crucial to achieving efficiency, maintainability, and performance. Two popular options for integrating Laravel on the backend with a JavaScript framework on the frontend are Nuxt and Inertia. In this article, we'll explore the advantages of Nuxt and why, after careful consideration, I came to the conclusion that when using Laravel, Inertia is often a better choice for seamless integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Advantages of Nuxt
&lt;/h2&gt;

&lt;p&gt;Before diving into the reasons behind choosing Inertia, let's first highlight some of the advantages of Nuxt, which is a powerful Vue.js framework designed to simplify frontend development:&lt;/p&gt;

&lt;p&gt;Server-Side Rendering (SSR): Nuxt offers built-in support for SSR, which can greatly improve SEO and page load times by rendering pages on the server before sending them to the client. This is beneficial for applications that require strong SEO and initial rendering performance.&lt;/p&gt;

&lt;p&gt;Code Splitting: Nuxt automatically splits your JavaScript code into smaller chunks, optimizing the initial load times of your application. This can lead to a better user experience, especially on slower network connections.&lt;/p&gt;

&lt;p&gt;Routing Made Easy: Nuxt provides a straightforward and flexible routing system that allows developers to define routes in a declarative manner. This simplifies the creation of complex routing structures.&lt;/p&gt;

&lt;p&gt;Vue Ecosystem Integration: Nuxt seamlessly integrates with the Vue.js ecosystem, allowing you to use Vue components and libraries without extra configuration.&lt;/p&gt;

&lt;p&gt;Out-of-the-Box Features: Nuxt comes with various pre-configured features like Vuex for state management, async data fetching, and automatic transitions between pages.&lt;/p&gt;

&lt;p&gt;Now, let's discuss why, despite these advantages, Inertia might be a more suitable choice for Laravel developers in certain scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inertia: My preferred choice for integrating Laravel and Vue.js
&lt;/h2&gt;

&lt;p&gt;Simplified Backend Logic: Inertia is designed to keep most of the logic on the backend side (using Laravel) while allowing for dynamic frontend updates. This is particularly advantageous when you have a Laravel-based API already in place, as it reduces the need for a separate frontend codebase and makes it easier to maintain the application.&lt;/p&gt;

&lt;p&gt;Familiar Blade Templates: With Inertia, you can continue to use Laravel's Blade templates on the frontend, reducing the learning curve for developers who are already familiar with Laravel. This means you can leverage the power of Laravel's templating engine without major changes.&lt;/p&gt;

&lt;p&gt;Seamless Data Fetching: Inertia provides a straightforward way to fetch and display data on the frontend without the need for complex API requests. It sends JSON responses from the backend directly to the Vue components, making data retrieval and rendering efficient.&lt;/p&gt;

&lt;p&gt;Server Setup Simplified: Unlike Nuxt, which introduces complexity in setting up a separate server for SSR, Inertia leverages the existing Laravel infrastructure, reducing the need for additional server configuration and maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nitro Server
&lt;/h2&gt;

&lt;p&gt;While looking into Nuxt, I learned about &lt;a href="https://nitro.unjs.io/"&gt;Nitro&lt;/a&gt;. Nitro is an open-source TypeScript framework designed to build ultra-fast web servers. If you need to build a performant application, comparing Nitro's performance with Laravel (using Octane) could be a valuable exercise, especially if speed and efficiency are critical for your project. I thought this is worth mentioning as one possible reason to consider using Nuxt over Laravel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, the choice between Nuxt and Inertia depends on your specific project requirements and team dynamics. In a team where frontend and backend developers work on separate codebases, using Nuxt could make more sense. In addition, Nuxt offers powerful SSR capabilities and a rich Vue.js ecosystem integration, but it can introduce additional complexity, particularly in cases where you want to maintain a single codebase for both frontend and backend.&lt;/p&gt;

&lt;p&gt;Inertia simplifies the integration of Laravel and Vue.js, making it a great choice for Laravel developers who want to leverage their existing backend expertise. &lt;/p&gt;

&lt;p&gt;Please leave a comment and let me know if you agree and disagree and what your experience is.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>laravel</category>
      <category>vue</category>
      <category>inertia</category>
    </item>
    <item>
      <title>Using Laravel Policy with middleware to protect routes</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Sat, 26 Aug 2023 11:11:32 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/using-laravel-policy-with-middleware-to-protect-routes-1h7</link>
      <guid>https://forem.com/programmingdecoded/using-laravel-policy-with-middleware-to-protect-routes-1h7</guid>
      <description>&lt;p&gt;So my goal was to only allow the logged in user to view or make changes to data that belongs to him. How do I know which data belongs to him? In the model &lt;code&gt;LanguagePack&lt;/code&gt; I save his user id. Any other data like the &lt;code&gt;WordList&lt;/code&gt; model has a relation to language pack. &lt;/p&gt;

&lt;p&gt;A user accesses the data via an url like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/languagepack/wordlist/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I don't want him to be able to change the id to 2 if that language pack wasn't created by him and then see and edit that data.&lt;/p&gt;

&lt;p&gt;To do this I created a &lt;a href="https://laravel.com/docs/10.x/authorization#policy-methods"&gt;policy class&lt;/a&gt; that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Policies;

use App\Models\LanguagePack;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class LanguagePackPolicy
{
    use HandlesAuthorization;

    public function view(User $user, LanguagePack $languagePack)
    {
        return $user-&amp;gt;id === $languagePack-&amp;gt;userid;
    }    

    public function update(User $user, LanguagePack $languagePack)
    {
        return $user-&amp;gt;id === $languagePack-&amp;gt;userid;
    }    

    public function delete(User $user, LanguagePack $languagePack)
    {
        return $user-&amp;gt;id === $languagePack-&amp;gt;user_id;
    }    
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I created a middleware for getting the language pack model from the route and running the policy check by &lt;a href="https://laravel.com/docs/10.x/authorization#via-middleware"&gt;authorizing actions&lt;/a&gt;. If the action is allowed, it will continue the request, otherwise it throws a 403 error and aborts the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class AuthorizeLanguagePack
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $languagePack = $request-&amp;gt;route('languagePack'); 

        if ($languagePack) {
            $user = $request-&amp;gt;user();

            if ($user-&amp;gt;can('view', $languagePack) ||
                $user-&amp;gt;can('update', $languagePack) ||
                $user-&amp;gt;can('delete', $languagePack)) {
                return $next($request);
            }

            abort(403, 'Unauthorized');
        }       

        return $next($request);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middleware has to be registered in the file &lt;code&gt;src/app/Http/Kernel.php&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected $routeMiddleware = [
     //add to the list of route middlewares
     'authorize.languagepack' =&amp;gt; \App\Http\Middleware\AuthorizeLanguagePack::class,
    ];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally we add the middleware key &lt;code&gt;authorize.languagepack&lt;/code&gt; to the routes in &lt;code&gt;web.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::middleware(['auth', 'authorize.languagepack'])-&amp;gt;group(function () {
 Route::get('/dashboard', [DashboardController::class, 'index'])-&amp;gt;name('home');    
    Route::get('languagepack/create', [LanguageInfoController::class, 'create']);
    Route::get('languagepack/edit/{languagePack}', [LanguageInfoController::class, 'edit']);
    Route::get('languagepack/wordlist/{languagePack}', [WordlistController::class, 'edit']);
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are probably other ways to achieve the same result. Let me know in the comments if you know of a better way...&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>security</category>
    </item>
    <item>
      <title>Why use git worktree?</title>
      <dc:creator>Philip Perry</dc:creator>
      <pubDate>Mon, 21 Aug 2023 19:39:12 +0000</pubDate>
      <link>https://forem.com/programmingdecoded/why-use-git-worktree-5gj0</link>
      <guid>https://forem.com/programmingdecoded/why-use-git-worktree-5gj0</guid>
      <description>&lt;p&gt;&lt;code&gt;git worktree&lt;/code&gt; is a command that I came across the first time today while browsing the git documentation. The example section explains a possible use case pretty well:&lt;/p&gt;




&lt;p&gt;From &lt;a href="https://git-scm.com/docs/git-worktree:"&gt;https://git-scm.com/docs/git-worktree:&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;You are in the middle of a refactoring session and your boss comes in and demands that you fix something immediately. You might typically use git-stash to store your changes away temporarily, however, your working tree is in such a state of disarray (with new, moved, and removed files, and other bits and pieces strewn around) that you don’t want to risk disturbing any of it. Instead, you create a temporary linked worktree to make the emergency fix, remove it when done, and then resume your earlier refactoring session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git worktree add -b emergency-fix ../temp master
$ pushd ../temp
# ... hack hack hack ...
$ git commit -a -m 'emergency fix for boss'
$ popd
$ git worktree remove ../temp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;While I'm not sure if the above scenario is something where I would use it, I can see it as a useful feature for experimenting. For example, I might have a half working solution that I could stash, but if I want to switch between one approach and another idea that I have, then I think using work trees would work much better (saves me from having to keep committing code that I'm not sure will work). What do you think? Have you used &lt;code&gt;git worktree&lt;/code&gt;?&lt;/p&gt;

</description>
      <category>git</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
