Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

A developer-centered approach to application development

July 3, 2020
Valentino Pellegrino
Related topics:
DevOpsNode.jsOpen source
Related products:
Red Hat OpenShiftRed Hat Enterprise Linux

    Do you dream of a local development environment that's easy to configure and works independently from the software layers that you are currently not working on? I do!

    As a software engineer, I have suffered the pain of starting projects that were not easy to configure. Reading the technical documentation does not help when much of it is outdated, or even worse, missing many steps. I have lost hours of my life trying to understand why my local development environment was not working.

    An ideal scenario

    As a developer, you have to meet a few prerequisites before contributing to a project. For instance, you must agree to the version-control requirements, and you need to know how to use the project IDE, how to use a package manager, and so on.

    But nothing more. You don't need to learn a poorly documented, made-in-house framework just to satisfy the ego of an architect who wanted to reinvent the wheel. You don't need to run an external virtual machine to emulate the production environment. As a developer, you are free to invest your time in improving the code and adding value to the product.

    A developer-centered approach to application development

    My goal with this article is to describe strategies for building an Angular 8 application in a way that centers the developer experience.

    The type of application is incidental. I describe a client application, but we could apply similar techniques to back-end modules. The framework, in this case, is Angular, but we could use similar techniques for practically any framework that you prefer.

    Note: As a brief introduction, Angular is an application design framework and development platform for creating efficient and sophisticated single-page apps. You can learn more on Angular website.

    The example application is a simple web app, with authentication, that performs several calls to REST endpoints. I won't offer many details about the domain and the business logic, because those factors don't matter for my discussion.

    The primary requirements for this use case are to enhance the developer experience. The strategies follow from that.

    Note: In cases where my strategies for resolving use-case requirements directly involve Angular and other software libraries, I will share details about those technologies. However, I am confident that similar options exist for other technologies and frameworks.

    Requirement 1: No back-end information in the client application

    Imagine the following scenario: A client-side application must perform a couple of GET operations, which will fetch data for display on a web page. How do you know which is the host address, the protocol, and the port to call for each REST endpoint?

    Typically, I have seen three approaches to resolving this issue:

    • Add the back-end information to the application at build time.
    • Pass the back-end information to the web application as parameters, or retrieve it from the environment variables.
    • Locate the web application and REST service on the same machine. This approach lets the web app call the localhost at a specific port and path. In that case, we "only" need to hard code the port and protocol.

    Unfortunately, each of these strategies leads to a black hole when developing your web application:

    • You need to modify the runtime status while debugging.
    • You need to hack the application to simulate the expected startup.
    • Worst of all, you need to point to a real shared dev or testing environment.

    Strategy: Reverse proxy

    The concept of a reverse proxy is quite easy. First, let's consider it as a black-box feature.

    Suppose that someone configures the machine that is hosting your web app so that when you call yourself (via localhost) on a specific path (for instance, /api), every call is automatically forwarded to the API server. With this configuration, it does not matter which is the address, the protocol, or the port in use.

    Note: If you want to look inside the black box, you can learn more about configuring a reverse proxy on Apache HTTPD or NGINX.

    Reverse proxy in Angular

    Now let's consider a reverse proxy in Angular, using a slightly different scenario. Suppose that your static files are served by the Webpack dev server on port 4200, while a Node.js app serves the APIs on port 3000. Figure 1 shows the flow of this architecture (Credit to https://juristr.com/blog/2016/11/configure-proxy-api-angular-cli/.)

    You can easily configure the global variable PROXY_CONFIG as part of the Webpack dev-server lifecycle. You can choose to use proxy.conf.json or proxy.conf.js, depending on your angular.json configuration file. Here's an example of a PROXY_CONFIG file:

    const PROXY_CONFIG = {
      "/api": {
        "target": "http://localhost:3000/",
        "secure": false,
        "logLevel": "debug",
        "changeOrigin": true
      }
    };
    
    module.exports = PROXY_CONFIG;
    

    Note that every HTTP call must point to /api. There is no need to specify any other information. The reverse proxy does the rest for us, like so:

    getPosts(): Observable {
      return this.http.get('/api/posts/');
    }
    

    As soon as you subscribe to getPosts(), it calls the target address (in this case, http://localhost:3000/posts).

    Note: Learn more about setting up an Angular CLI reverse proxy or a Webpack dev server reverse proxy.

    Requirement 2: Offline coding (coding without an Internet connection)

    When coding, you want your dependencies with the outside world to be as minimal as possible. There are many reasons to avoid connecting to a shared remote development machine. The remote machine might be:

    • Not recently updated.
    • Slow, because of its load.
    • Delayed, because there is a VPN.
    • Unavailable, because someone is updating it.
    • Unreachable, because your Internet connection is not working.

    You also don't want to launch a real instance of the development machine locally, however. Such an instance might:

    • Have third-party dependencies that are difficult to mock.
    • Be heavy to run, for instance, with a minimum requirement of 32GB of RAM.
    • Be connected to a database, in which case you have to either install the database or connect to a real remote instance.
    • Be difficult to update because your data are in a historical series, so what is valid today might not be valid tomorrow.

    Strategy: Mocking data

    There are several solutions to make development fast and agile. For example, you could use containers to provide isolated and reproducible computing environments.

    When working on a web app, I believe it makes sense to use mocked APIs. If you are working with REST endpoints, I recommend the json-server package, which you can install both globally and locally. If you install json-server globally, you can launch it anywhere you like. If you install it locally, you can install it as a dependency for your dev environment, and then create a Node Package Manager (npm) script to launch a customized mocked server.

    The setup is quite intuitive. Say that you have a JSON file as a data source; say, db.json:

    db.json:
    {
      "posts": [
        { "id": 1, "title": "json-server", "author": "typicode" }
      ],
      "comments": [
        { "id": 1, "body": "some comment", "postId": 1 }
      ],
      "profile": { "name": "typicode" }
    }
    

    You can launch the file via the command line:

    $ json-server --watch db.json

    By default, it starts on localhost, port 3000, so if you GET http://localhost:3000/posts/1, you will receive the following response:

    { "id": 1, "title": "json-server", "author": "typicode" }
    

    The GET is just an example, you can use other HTTP verbs, as well. You also can choose to save edits in the original file or leave it as it is. Exposed APIs follow the REST standard, and you can sort, filter, paginate, and load remote schemas.

    As I mentioned earlier, you can create your own script and run a json-server instance programmatically:

    const jsonServer = require('json-server')
    const server = jsonServer.create()
    const router = jsonServer.router('db.json')
    const middlewares = jsonServer.defaults()
    
    server.use(middlewares)
    server.use(router)
    server.listen(3000, () => {
      console.log('JSON Server is running')
    })
    

    Mocked data in Angular

    I can suggest a couple of strategies for making your Angular app work with mocked data. Both are based on the proxy.

    Strategy 1: Configure the reverse proxy, pointing to http://localhost:3000/ in the target, so that every call points to the json-server instance.

    Strategy 2: Add a custom mocking rule to the proxy, so that it uses the bypass parameter to return data for a specific path:

    const PROXY_CONFIG = {
      '/api': {
        'target': 'http://localhost:5000',
        'bypass': function (req, res, proxyOptions) {
          switch (req.url) {
            case '/api/json1':
              const objectToReturn1 = {
                value1: 1,
                value2: 'value2',
                value3: 'value3'
              };
              res.end(JSON.stringify(objectToReturn1));
              return true;
            case '/api/json2':
              const objectToReturn2 = {
                value1: 2,
                value2: 'value3',
                value3: 'value4'
              };
              res.end(JSON.stringify(objectToReturn2));
              return true;
          }
        }
      }
    }
    
    module.exports = PROXY_CONFIG;
    

    Requirement 3: Dev code should not affect production code, and vice versa

    How many times have you seen something like this:

    if (devMode) {...} else {...}

    This code is an example of what we call code smell, meaning that it mixes code for development purposes with code intended for production only. A build targeted for production should not contain code related to development, and vice versa. The solution to code smell is to use different builds for different targets.

    Code smell shows up in many different kinds of use cases. For instance, your application could be hosted behind a single sign-on (SSO) authentication system. The first time that a user requests the application in a browser, the request is redirected to an external page, which asks for credentials.

    When you are in dev mode, you don't want to deal with the redirect. A less complicated authentication service is welcome.

    Strategy: Use a file-replacement policy

    In Angular, based on the current configuration, it is possible to specify a file-replacement policy. You can easily use this feature to replace a simple authentication service used for development purposes with a more robust and complex one required for production:

    "configurations": {
      "production": {
        "fileReplacements": [
          {
            "replace": "src/app/core/services/authenticator.ts",
            "with": "src/app/core/services/authenticator.prod.ts"
          }
        ],
        ...
      ...
    }
    

    The codebase now has two separate authentication services, which are configured for use in two different environments. Most importantly, only one service will be included in the final artifact, based on the specific build parameter:

    $ npm run ng build -c production

    Requirement 4: Know what version of the application is currently running in production

    Do you know at all times what version of your application is running on a given host? You can use build parameters like build time or the last-commit identifier to determine whether your current environment is updated for a recent update or bug fix.

    Strategy: Use angular-build-info

    Angular includes a command-line tool, called angular-build-info, that produces a build.ts file inside of your Angular project's src/ folder. Using this tool, you can import the build.ts file inside of your Angular application and use the exported buildInfo variable:

    import { Component } from '@angular/core';
    import { environment } from '../environments/environment';
    import { buildInfo } from '../build';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss']
    })
    export class AppComponent {
      constructor() {
        console.log(
          `\nBuild Info:\n` +
          ` ❯ Environment: ${environment.production ? 'production ?' : 'development ?'}\n` +
          ` ❯ Build Version: ${buildInfo.version}\n` +
          ` ❯ Build Timestamp: ${buildInfo.timestamp}\n`
        );
      }
    }
    

    Note that the build.ts content must be versioned, so you need to execute the following script at build time:

    $ angular-build-info --no-message --no-user --no-hash
    

    The parameters are optional so that you can customize the produced buildInfo.

    Requirement 5: A fast and effective quality check in the pipeline

    Regardless of whether you are launching a build pipeline locally or if you have sent a pull request, it would be great to have an overview of the overall project quality.

    Strategy: Static code analysis with a quality gate

    When you need to measure the quality of a software, static code analysis might help. It provides several metrics about readability, maintainability, security, etc. without actually execute the software itself.

    If you are able to measure quality metrics, then you can configure formal revisions that might help to evaluate the process used to develop and release new parts of the software. Such formal revisions are named quality gates.

    Static code analysis must be fast, with clean results. You don't want to scroll through pages of redundant logged results. It matters—the phase, and the order, where you place the quality gate.

    For this requirement, I would place the quality gate before test execution and immediately after compilation or transpiling (assuming that is happening). I recommend this placement for two reasons:

    1. It avoids wasting time checking the static code if it does not compile or transpile.
    2. It avoids wasting time executing a whole suite of tests for code that does not meet the minimum requirements that the team has defined.

    It is important to keep in mind that a pipeline execution requires resources. A good developer should never push a commit without executing a local quality check first. You can also reduce the number of files to be checked by caching the results, or performing static code analysis, only on files that are involved in the change list.

    Conclusion

    When you start working on a new project, non-technical requirements should not slow down your productivity curve.

    As a developer, you should not have to waste time on configuration issues, or a development machine that sometimes works and sometimes doesn't. Take care of these issues up-front. Happy developers spend more time coding than resolving technical impediments.

    Improving your developer experience is not a one-time process, but an incremental one. There is always room for automation. There is always room for improvement.

     
    Last updated: June 13, 2022

    Recent Posts

    • Every layer counts: Defense in depth for AI agents with Red Hat AI

    • Fun in the RUN instruction: Why container builds with distroless images can surprise you

    • Trusted software factory: Building trust in the agentic AI era

    • Build a zero trust AI pipeline with OpenShift and RHEL CVMs

    • Red Hat Hardened Images: Top 5 benefits for software developers

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Chat Support

    Please log in with your Red Hat account to access chat support.