Introduction

Nectry is a platform for rapid development of enterprise software without programming. Plenty of competing tools under the headings of “no-code” and “low-code” make similar claims, and here we’ll present Nectry’s technical foundation, which we hope gives a sense of how it is rather different from the competition. Note that Nectry offers a “no-code” view where users need not know almost anything covered in this document! However, for a summary of the underpinnings and to inform more technically inclined users who want the “low-code” view, read on.

In programming classes, we teach students to be very careful about copying and pasting code. It’s easy to propagate a bug across a code base, making it hard to correct the bug when it’s found later. And code full of lengthy copy-and-paste replicas is just plain hard to read, because of how long it gets. The reader is tempted to brush past the apparent replicas, even if they may actually have small differences that affect functionality. The alternative is reading and understanding all of that code each time it comes up, which is rather time-intensive.

In the 21st century, programmers started relying on web sites like StackOverflow to answer their programming questions. So far so good. The trouble is that code snippets are often copied and pasted from these sites, without sufficient understanding that those snippets are really fit for purpose. We get all the same problems of old-school copy-and-paste, and we add the risks of trusting random posters on web sites to give correct answers. Prepare to be creeped out by this quote from a research paper:


Our results are alarming: 15.4% of the 1.3 million Android applications we analyzed, contained security-related code snippets from Stack Overflow. Out of these 97.9% contain at least one insecure code snippet.


Enter AI copilots, starting with the big splash from ChatGPT’s release. They magically write code for you from English requirements! Trade in your coding keyboard for a beach chair and a drink! When AI copilots work well, they effectively automate finding the right snippets to copy and paste together for you. Notice that this variant already brings back all of the downsides we covered above, including introducing bugs from untrusted sources and settling on highly verbose code bases that are hard to read and therefore hard to modify directly. But now we also have the risk that the code was written by a strange alien being (a machine-learning model) rather than just human programmers who accidentally post bad code. The possibilities for crazy bugs are greater than ever – and good luck finding them in long Frankenstein’s-monster code bases full of code snippets adapted from here and there.

It’s undeniable that programming is a tedious and repetitive progress, especially in the world of enterprise software, where each new solution tends to be highly derivative of those that came before. Is there a reliable way we can take advantage of the latest AI advances to streamline development? Nectry embodies one answer!

The Big Idea of Nectry

Software-engineering gurus have been preaching code reuse since dinosaurs walked the earth. Instead of copying and pasting a snippet embodying a good idea, call a library function, allocate an object of a library class, or so on. Give good ideas first-class status in libraries and make them easy to invoke by name. It’s kind of the antithesis of the search-StackOverflow approach. Every popular StackOverflow answer is evidence that some library was missing a nice named component that should have made for a one-line answer!

Nectry leans into this idea and helps programmers build applications solely by choosing, configuring, and composing library components. However, mainstream programming languages don’t have expressive-enough module formalisms to let us write components as high-level as we want. We want full-stack components that stand for complete feature units meaningful to nonprogrammers, like “ticket system.” Such a component should build all code for its feature associated with frontend, backend, and database tiers. Components should be able to adapt to the data models of applications, e.g. a ticket system in a factory might link tickets to rows in a database table of products in the catalog, while a ticket system for a university class might link tickets to rows in a table of homework assignments. Different additional fields might be appropriate for tickets in each context. We should be able to tell the general component which slight variations on its theme we want, and then it generates all of the code for us.

This description probably makes it clear that the approach we’re advocating isn’t a good fit for high-novelty applications, for instance an application worth calling a product. Instead, think of Nectry as more oriented toward internal tools, where a single tool category often exists in moderately different forms across companies. Nectry is also great for creating applications that are used by customers, suppliers, event participants, and more, but we don’t know a good standard phrase like “internal tools” to name this broader category.

The Ur/Web programming language was designed to realize this vision, and UPO (the Ur/Web People Organizer) is a library stocked with components of just the unusual kind we just described. These tools are designed for programmers, supporting programming in a Turing-complete language.

Nectry Builds Low-Code and No-Code Layers on Top of Ur/Web

More technically minded people can write programs in our new configuration language NectryCore, which is very far from Turing-complete, featuring no loops, function calls, or even sequencing of statements. NectryCore configurations just name library components and explain how to put them together into applications, and our compiler and deployment tooling takes it from there.

That’s the “low-code” view of Nectry, but we also support a fully “no-code” view where an LLM-powered chatbot writes the NectryCore code for you, based on conversations in English. More advanced users are always free to look at the generated NectryCore and tinker with it directly, and even less-advanced users can read reliable renderings of NectryCore as English, to spot and correct mistakes with zero trust of AI frontends.

The rest of this document introduces NectryCore by example. We’re going to start with a blank Nectry application, which looks like the following.

A blank Nectry application

A Note on Workflow

Nectry isn’t in public beta yet, and anyone who wants to use it needs to work directly with the company (contact us if interested!). We don’t yet publish any tools for doing anything with the configuration language that this tutorial documents. Instead, we need to set you up in our web-based IDE, which includes a compiler for NectryCore. There will probably be command-line tools eventually for working with NectryCore files.

Think of our NectryCore compiler as producing Docker containers, with standalone web servers inside, that you can deploy wherever you want. The details are largely orthogonal to what is really new and interesting about Nectry, and we don’t bother with them in this tutorial, just presuming that generated web apps pop up somewhere to be used.

Database Tables

Every Nectry application has its own SQL database at its core. We bring other relevant state into that database to connect it to library components. Let’s start by defining a single database table in NectryCore.

items:
  - table:
      name: Programming Language
      columns:
        - key: true
          name: Name
          type: string
        - name: Invented in year
          type: int
          required: true

We present a standard SQL table schema, just in a funny YAML format. Note that we do not enforce usual rules about names of tables and columns, e.g. our table and column names may contain space characters. These names will often be included for us in UIs that Nectry generates, so it pays to make them approachable for the target user audience of our app.

We have defined a complete NectryCore application, but it doesn’t do anything yet. Let’s create a simple CRUD interface to our one database table, so that we can start with some minimal application functionality. The following code snippet is added after the last one we showed (so the database table remains part of the app configuration).

  - handler:
      name: main
      title: Main
      tabs:
        - name: Languages
          icon: mdi_view-list
          segments:
            - - module:
                  name: Languages
                  concept: Table View
                  settings:
                    tab: "Programming Language"

Now we have a web app with a single page named “Main,” with a URL ending in main. It includes a navbar that will eventually list multiple tabs of content, though for now we just included one tab. It is labeled “Languages” and lets us view and modify the table of programming languages. We put an icon next to it, referencing one of the icon images from the Material Design framework.

The real action is in invoking one of our library components, which Nectry calls concepts. The one we choose is “Table View,” and we call our instance of it “Languages,” too. It stands for the classic CRUD functionality over one SQL table. In general, concepts may require a variety of settings to explain how we specialize them to our application and data model. Here, we just need to provide one setting called tab, to specify which database table should be shown/edited.

The application now looks like the following (after we use the form to add a few languages), where we can see that the left navbar is used to switch among the tabs listed in a page’s configuration. Each navbar entry has the chosen icon next to it.

A simple table view

Note that we already have a full-stack web application! We didn’t need to design a single UI or define a single HTTP AJAX endpoint, though both of those aspects are found in the code that is compiled automatically from the NectryCore file. In fact, Nectry does not allow much visual customization in general. We like to say that we adopt the Henry Ford model of user experience (UX): you can have any professional-grade UX you want, as long as it’s the one our team coded into the Nectry component library.

Ingredients

To set up a more complex example, let’s add a new table with the following code right after the table definition in our existing example.

  - table:
      name: Program
      columns:
        - key: true
          name: Name
          type: string
        - name: Language
          type: Programming Language
          required: true
        - name: Commentary
          type: string

Now we’re storing famous programs, where each has a name, language it’s written in, and optional commentary. Note the type of the Language field: it’s not a base type but a foreign key reference to the programming language it is written in.

Let’s add a form that visitors can submit to tell us about their favorite programs. We’ll use a new important NectryCore element called ingredients. The following code can be added at the end of our configuration.

         - name: "Programs"
             icon: "mdi_plus-circle-outline"
             segments:
               - - module:
                     name: Submit Program
                     concept: Complex Form Entry
                     settings:
                       buttonText: "Add New Program"
                       tab: "Program"
                       rowIngredients:
                         - kind: NormalWidget
                           settings:
                             cssWidth: 6
                             nm: "Name"
                         - kind: NormalWidget
                           settings:
                             cssWidth: 6
                             nm: "Language"
                         - kind: NormalWidget
                           settings:
                             cssWidth: 12
                             nm: "Commentary"

We have gotten more adventurous, choosing a concept called “Complex Form Entry.” It’s for forms that, upon submission, add entries to tables. In its settings, we say which table tab to submit to, as well as what buttonText to put on the submission button. The new functionality is linked from a new entry in the navbar of the application, with a nice little “plus” icon next to it.

More interestingly, we have some ingredients, or nested invocations of library components of different types, which here we use to specify how different fields of a form should work. We accepted the default behavior of that kind, in our previous use of the more basic “Table View” concept. With “Complex Form Entry,” as the name implies, we have the chance to customize.

Here is the new tab we have designed.

A form to add a program

Actually, our use of ingredients here is pretty tame and adds little beyond accepting the default behavior. The foreign-key column “Language” does get treated properly with a dropdown listing all languages in the database, but “Table View” would do that for us, too. Let’s consider how to get more creative specifying behavior of columns.

A realistic scenario is that this app is run by a programming-languages club that is full of snobs. They only want to accept new submissions for classic programming languages, meaning designed before this century. Such a requirement is easily accomplished by editing the ingredient entry for the “Language” column.

                         - kind: QueryDropdown
                           settings: 
                             cssWidth: 3
                             nm: "Language"
                             query: >-
                               SELECT "Programming Language"."Name" AS "Id", "Programming Language"."Name" AS "Label"
                               FROM "Programming Language"
                               WHERE "Programming Language"."Invented in year" < 2000

We use a component called QueryDropdown that runs an SQL query to determine what entries to show in a dropdown. The query appears as a setting here, in Nectry’s funny-looking, work-in-progress query syntax. In general, such a query should give for each entry both its foreign-key value and the appropriate display text to include in the dropdown. Note that the Nectry compiler does type-check this SQL query for us in advance, to guarantee that it never leads to a runtime exception. Indeed, it is even turned into an SQL prepared statement, for maximum efficiency of serving page requests. And as one more advantage of Nectry’s full understanding of SQL, our tooling can warn when a schema change would invalidate existing configuration (or even apply an automatic refactoring to all queries, for simple-enough schema changes).

The dropdown now filters options properly, in addition to including a search box.

An enhanced form to add a program

Times and Authentication

Our programming-language club is very exclusive, and we need to authenticate properly to be sure no commoners get in. Let’s add a database table recording authorized users, who of course are invited to declare their favorite programming languages. Let’s also modify the table of programs to record which club member submitted each program.

  - table:
      name: User
      columns:
        - key: true
          name: Name
          type: string
        - name: Favorite language
          type: Programming Language
  - table:
      name: Program
      columns:
        - key: true
          name: Name
          type: string
        - name: Language
          type: Programming Language
          required: true
        - name: Commentary
          type: string
        - name: Submitter
          type: User
          required: true

Reasonable people will differ on whether it makes sense to include users’ favorite programming languages inline in the table of users, but part of our point here is that Nectry is flexible and allows that schema design choice, among many others.

Now we need to add an authenticator to handle login.

   - authenticator:
       name: Dummy
       provider: Dummy
       table: User

The provider names one of another kind of library component, this time for different ways of logging users in. We’ve built a few for common single-sign-on providers, but for simplicity we stick to a dummy one here, which allows anyone to log in as anyone. An authenticator is connected to a database table of users, which here includes the one with a favorite-language column that the author of the authentication provider certainly didn’t plan for specially. Luckily, authenticators, like other Nectry components, are rather polymorphic in the parts of schemas they are connected to. For this provider, there just needs to be a Name column, and the authenticator will go ahead and add new users as they log in for the first times. (A more industrial-strength authenticator, like for a standard single-sign-on provider, would require that the claimed user actually exist, not to mention that the right password or similar credential was provided!) Note that, for now at least in the design of Nectry, authenticators are a special kind of component, separate from concepts as we used previously.

Now a user faces this logon screen upon beginning a session.

Login form

Now here is the modified configuration for our main page handler. See if you can spot the two changes before we describe them next!

   - handler: 
      name: main
      title: Main
      authentication:
        authenticator: Dummy
      tabs:
        - name: Languages
          icon: mdi_view-list
          segments:
            - - module:
                  name: Languages
                  concept: Table View
                  settings:
                    tab: "Programming Language"
        - name: Programs
          icon: mdi_plus-circle-outline
          segments: 
            - - module:
                  name: Submit Program
                  concept: Complex Form Entry
                  settings: 
                    buttonText: "Add New Program"
                    tab: "Program"
                    rowIngredients: 
                      - kind: NormalWidget
                        settings: 
                          cssWidth: 3
                          nm: "Name"
                      - kind: QueryDropdown
                        settings: 
                          cssWidth: 3
                          nm: "Language"
                          query: >-
                            SELECT "Programming Language"."Name" AS "Id", "Programming Language"."Name" AS "Label"
                            FROM "Programming Language"
                            WHERE "Programming Language"."Invented in year" < 2000
                      - kind: NormalWidget
                        settings: 
                          cssWidth: 3
                          nm: "Commentary"
                      - kind: UserField
                        settings:
                          nm: "Submitter"

We added an authentication field to say that main should require that folks log in via the authenticator we created, and we added a final ingredient at the bottom, for the “Submitter” field of a language. We invoke a UserField component, which automagically fills in this field with the name of the logged-in user. This change doesn’t actually modify the visible UI, so we won’t include a new screenshot.

Note that an authenticator’s login prompt only appears when visiting a page declared as requiring authentication via that authenticator. A visitor can’t see the contents of a protected page without authenticating by the prescribed method.

Let’s show off one more chance to customize form handling. It’s just good bookkeeping to remember when each program was submitted, so we add a column.

  - table:
      name: Program
      columns:
        - key: true
          name: Name
          type: string
        - name: Language
          type: Programming Language
          required: true
        - name: Commentary
          type: string
        - name: Submitter
          type: User
          required: true
        - name: Submitted
          type: time
          required: true

We also add one more ingredient at the end of the whole configuration.

                      - kind: CurrentTime
                        settings:
                          nm: "Submitted"

At this point you can probably guess what the last snippet is all about! Actually, let us take this opportunity to show off advantages of a nice, structured language like NectryCore. What would happen if we added the following snippet instead?

                      - kind: CurrentTime
                        settings:
                          nm: "User"

Oops, we accidentally asked to stash the current time in the User column, which is a type error (type string in the database schema, where time is needed). Not to worry: the NectryCore compiler will warn us of the type error, because it has a thorough understanding of all of the parts of an application.

This stage in the document is a good one for us to explain a larger design principle we’re seeing at work. Many no-code tools will allow configuration of their limited functionality with YAML or JSON files or something. We mean “limited” in a somewhat positive way: by forcing all apps to fit a single model (e.g. spreadsheet-looking UIs), they can limit the design space and more easily achieve good usability. However, Nectry is different in building on an open component architecture that allows developing all kinds of different user experiences and modes of data usage.

Very little that we showed above is built into Nectry. Components like QueryDropdown and CurrentTime, even higher-level concepts like “Custom Form Entry,” can be added without modifying the Nectry engine. They are implemented in Ur/Web and type-checked in isolation, with respect to the formal interfaces that they declare via type signatures. For now, we’re only letting the Nectry engineering team add new components, but we may expose that capability more broadly eventually. Even with just us doing the work to add components, every unit of addition automatically picks up streamlined support in the Nectry development environment.

You can find a variety of examples in the UPO source code, but as just one more level of hand-waving summary: think of each of these components as a class, in the sense of object-oriented programming. For instance, classes QueryDropdown and CurrentTime can be seen as implementing a common interface, with a method for generating HTML for their visual renderings, as well as a method for retrieving the value that has been entered into each. Custom Form Entry “simply” weaves together calls to those methods, to create seamless frontend, backend, and database logic. It isn’t so trivial in practice, since there is fancy static type-checking of components, to make sure they work properly in any scenarios they may be dropped into.

Analytics

It’s easy enough to add other elements like graphing of SQL query results. Here’s all we need to add to graph how many programs are in the database per language, as well as how many programs were submitted per user. We show off including two modules on a single tab. (You may have realized that segments is a list of lists of modules, which in general can be used to describe a two-dimensional grid layout of content.)

         - name: Analytics
           icon: mdi_chart-bar
           segments:
              - - module:
                    name: Program Count by Language Graph
                    concept: Graph from Query
                    settings:
                      query: >-
                        SELECT "Program"."Language" AS "Programming Language", COUNT("Program"."Name") AS "Number of Programs"
                        FROM "Program", "Programming Language"
                        WHERE "Program"."Language" = "Programming Language"."Name"
                        GROUP BY "Program"."Language"
                      graphTypeString: "Bar"
              - - module:
                    name: Most Prolific Members
                    concept: Graph from Query
                    settings:
                      query: >-
                        SELECT "User"."Name" AS "User", COUNT("Program"."Name") AS "Number of Programs"
                        FROM "User", "Program"
                        WHERE "Program"."Submitter" = "User"."Name"
                        GROUP BY "User"."Name"
                      graphTypeString: "Line"
Some graphs

Again, what if we made a small mistake in the code? What if that final snippet were instead the following?

              - - module:
                    name: Most Prolific Members
                    concept: Graph from Query
                    settings:
                      query: >-
                        SELECT "User"."Name" AS "User", "Program"."Name" AS "Number of Programs"
                        FROM "User", "Program"
                        WHERE "Program"."Submitter" = "User"."Name"
                      graphTypeString: "Line"

We forgot to use aggregation, so instead of returning a count of programs, we return program names, which is nonsensical from a graphing perspective. The NectryCore compiler informs us that it really needed to see an int in that position. That is, the compiler parses and type-checks SQL, with respect to the true schema of the application. Sometimes SQL queries even reference NectryCore variables, like those standing for inputs of a page handler, and these parameters are checked with the right types that guarantee consistency within the application.

Data Integration

Entering all of this valuable data over web forms gets old quickly! Let’s create a flow for importing languages from CSV files.

First, recall that Nectry tries to make all the world look like one big SQL database. We create tables in the app’s private database to mirror relevant content from elsewhere in the world. To that end, we make a table to store the contents of an uploaded CSV file.

   - table:
       name: Imported Language
       columns:
         - key: true
           name: Name
           type: string
         - name: Year
           type: int
           required: true

Then we add a new tab to our page, for the UI of importing a CSV file into the new table.

        - name: Import Languages
          icon: mdi_upload
          segments:
             - - module:
                   name: Import CSV to Language Table
                   concept: CSV Import
                   settings:
                     tab: "Imported Language"
CSV import

Now, this CSV file came from a dusty old archive somewhere, and we don’t quite trust its contents. There may be languages unbefitting of inclusion in our database, and there may be duplicates of existing languages, with slight spelling differences. Therefore, let’s institute an explicit merging process, to push a subset of these languages into our authoritative table. We’ll simply invoke another concept from our library, in a new segment of the tab we already created for importing languages.

             - - module:
                   name: Copy Imported Data to Main Table
                   concept: Fuzzy matching
                   settings: 
                     maxHypotheses: "5"
                     src: "Imported Language"
                     dsts: 
                       - kind: OneTable
                         settings: 
                           default_to_add: true
                           dst: "Programming Language"
                           addons: 
                             - kind: AddonWithColumn
                               settings: 
                                 dcol: "Name"
                                 addon: 
                                   - kind: FuzzyMatchColumn
                                     settings: 
                                       scol: "Name"
                             - kind: AddonWithColumn
                               settings: 
                                 dcol: "Invented in year"
                                 addon: 
                                   - kind: NonMatchingColumn
                                     settings: 
                                       scol: "Year"

You should be prepared by now to understand most of what is going on in this new example, at least from a structural perspective. On display is the fact that, while NectryCore is intentionally not Turing-complete, it harnesses the full combinatorial power of language to describe complex uses of libraries. Ingredients of one type may have nested ingredients of another type!

Let’s walk through some of the high points of this chunk of configuration. We use a concept “Fuzzy matching,” which generalizes the idea of intelligently copying rows from one table to another. We tell it the source table src is “Imported Language,” which is fed by the CSV-import module we made previously. In general, rows of a given source table could be chosen to copy to a number of destination tables. Hence, “Fuzzy matching” exposes ingredients to explain different strategies of sending results to tables. In this case, we invoke just a single compatible ingredient kind OneTable, where we reveal that the destination dst is “Programming Language.” Then we have nested ingredients explaining how to populate each column of the destination table. Every column gets a use of AddonWithColumn to avoid defining shared settings in multiple places, and then we use a FuzzyMatchColumn to ask that language names be compared for fuzzy similarity to spot duplicates, while a NonMatchingColumn explains that years are merely along for the ride and not compared in duplicate detection. For those of you keeping score at home, that was four levels of nesting of library components of different types!

This new module presents an interface listing all languages we imported, with a suggestion for how to handle each: declare it a duplicate of a particular existing language, add it as a new one, or just skip it. (In general, different behavior could be triggered for the first vs. third of these choices, in more complex uses of fuzzy matching.) The user is free to click to override any choice, before clicking another button to confirm the mass merge.

Merging CSV entries

Connecting to Remote Services

Nectry also makes it easy to connect to name-brand SaaS services (well, at least the ones we already implemented connectors to). Let’s integrate with a Salesforce instance. First, we create two mirror tables that will stand for parts of a remote Salesforce database.

  - table: 
      name: Salesforce Account
      columns: 
        - name: Name
          type: string
          key: true
  - table: 
      name: Salesforce Contact
      columns: 
        - name: Name
          type: string
          key: true
        - name: Account
          type: Salesforce Account
          required: true
        - name: Email
          type: string

Next we configure a service connection to Salesforce, using different kinds of Nectry library components.

  - service: 
      name: Salesforce
      provider: Salesforce
      api: 
        provider: Salesforce
        settings: 
          client_id: "HIDDEN"
          client_secret: "HIDDEN"
          instance: "HIDDEN"
          sandbox: false
      linkedTables: 
        - table: Salesforce Account
          remoteTable: Account
          mode: read_write
          kind: Table
        - table: Salesforce Contact
          remoteTable: Contact
          mode: read_write
          kind: PersonTable

The occurrences of provider: Salesforce are naming two different kinds of library components: one for APIs, a more primitive notion of connecting to a remote service; and one for services, which brings in the idea of replicating part of the remote database within the Nectry application’s database. Nectry unifies the wide variety of services by making all of them look like subsets of the local database, handling the plumbing of pushing records in both directions as needed. That way, we can use very general full-stack components alongside all kinds of combinations of external services, since they’ve all been made to look like SQL.

The further configuration above provides authentication settings for Salesforce (censored here so you don’t try to hack us!) as well as the explicit linking of remote tables to local ones. Each such linking specifies in which directions data flows (between Nectry app and remote service) with mode fields, as well as tagging each table with a kind, referencing yet another kind of component specific to the service. In this case, we trigger some special savvy for the Salesforce Contact table based on knowing that it represents people, via invoking the PersonTable component.

The point is that, with this configuration set up, Salesforce now looks like just a part of our database, and we can use exactly the same kinds of NectryCore configuration as above, for instance to display tables, graph the results of queries over them, or involve them in data-integration processes that also touch other services’ data.

We should also add that Nectry’s IDE includes a wizard for generating service configuration. The user just picks the name-brand service to integrate and steps through a checklist that we wrote to provision an API connection in that service and copy the relevant authentication fields into Nectry. The IDE also queries the service for its data schema and guides the user through selecting which parts to mirror.

Connecting to Custom Web Services

Suppose our club has a custom legacy web service maintaining a database of programming languages, and we want to import its contents to our new Nectry app. Luckily, there is already a specification for that service lying around, in the quasi-industry-standard format OpenAPI. Here is the API specification. (Don’t get thrown off by the fact that it’s also in YAML syntax. It’s not NectryCore syntax!)

openapi: 3.0.0
info:
  title: Programming Languages API
  version: 1.0.0
  description: A simple API that returns a list of programming languages and their release years.
servers:
  - url: http://127.0.0.1:5000
    description: Local server
paths:
  /languages:
    get:
      summary: Get a list of programming languages
      description: Returns a list of programming languages with their release years.
      responses:
        '200':
          description: A JSON array of programming languages
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  required:
                    - name
                    - year
                  properties:
                    name:
                      type: string
                      example: Python
                    year:
                      type: integer
                      example: 1991

OK, so we can perform a GET request to http://127.0.0.1:5000/languages to retrieve a list of programming languages. That should be enough to import the data we need.

In fact, the Nectry IDE includes a wizard wherein we upload an OpenAPI spec and choose how to present the corresponding API as a set of mirror tables. Going through that process, we get the following NectryCore code written for us.

   - service:
       name: LangDB
       provider: Custom
       api:
         provider: No authentication
         endpoints:
           - method: GET
             url: http://localhost:5000/languages
             responseBody:
               list: true
               type:
                 record:
                   - name: name
                     type:
                       base: string
                     required: true
                   - name: year
                     type:
                       base: int
                     required: true
       linkedTables:
         - table: LangDB
           remoteTable: Languages
           mode: read
           get:
             url: http://localhost:5000/languages
             columns:
               - remote: name
                 local: Name
               - remote: year
                 local: Invented in year

Note how the linked-table specification includes a mapping between field names in the API and nicer field names we prefer to work with locally. (Again, it pays off to make them nice because they will appear in automatically generated UIs.)

The wizard also automatically added a table entry (not shown above) for the new LangDB table we define. Now this table is automatically fed from the API, and we can use it just like any of the other tables we’ve worked with. Not shown here is the ability to create read-write table connections, via using other API endpoints (canonically POST) to send new rows to the web service.

Reflecting on Type-Checking

The process we just went through may seem similar enough, on one level, to conventional app development with Python or whatnot, but we’d like to pause here to highlight some key differences. A Nectry app is defined in a unified configuration language, NectryCore, and usually even laid out in a single file. Contrast that state of affairs with using separate programming/template languages for frontend, backend, and database tiers in other frameworks. Not only is it a chore to switch between files in an IDE (if you’re lucky enough to have one that supports all the languages well), but programming tools typically fail to understand cross-language interaction. Even with e.g. all-JavaScript approaches, there is typically minimal help understanding cross-tier interactions up-front.

Nectry is different: every version of the application configuration is statically type-checked to avoid the most common kinds of bugs, security vulnerabilities, and inconsistencies! Here are just a few examples of bugs that can never exist in a Nectry application that passes type-checking. (Some examples draw on NectryCore features that we haven’t yet added to this tutorial.)

  • Any HTML or CSS code is invalid.
  • Hyperlinks within the application lead to “404 Not Found” errors or subtler errors about missing query parameters or invalid formatting of the parameter values.
  • A JavaScript bug leads to failure to look up a node in the DOM or modify it properly.
  • A supposed SQL query is sent to the database, but it is syntactically invalid or makes wrong assumptions about the database schema.
  • The application frontend calls a backend REST API method that doesn’t exist, or the frontend tries calling the backend method with the wrong number or types of parameters, or the frontend makes a wrong assumption about the type of value returned by the backend.
  • The application backend misunderstands the REST API of an external service and makes one of the above errors.
  • A cross-site scripting bug allows attackers to submit forms with field values that are accidentally interpreted as HTML, SQL, or JavaScript. (See Little Bobby Tables.)

We aim even higher, by stocking our library only with components that generate high-quality user experience, though we admit this guarantee is much less formal than the above!

What’s Next?

We have some cool security features in mind for Nectry, to check that NectryCore configurations don’t violate information-flow policies. Those features are work in progress and certainly not documented here, but you can check out a research paper that is the intellectual inspiration.

We also hope to have documentation soon on our full library of components, usable in NectryCore configurations.

Let's connect and explore opportunities together.

Contact Us