Back to List

Building an Experiment Data Server with Express

Build an experiment data API server with Express.js. Learn routing, middleware, and REST APIs through bio examples.

Beginner
|
60min
|
Verified (2026-06)
ExpressNode.jsServerRoutingMiddlewareREST API
Progress0/19 (0%)

Building an Experiment Data Server with Express

So far you've learned to request data from a server using fetch. Now we'll build the other side โ€” the server that sends the data.

Just as labs use LIMS (Laboratory Information Management System), web applications need a server that stores and retrieves data. Express is the tool for building that server. It's a web framework that runs on top of the Node.js you learned earlier โ€” think of it as a server development kit.

You can build a server with Node.js alone (remember http.createServer?), but you'd have to implement everything yourself. Express handles repetitive tasks like serving different data based on URLs, providing files, and handling errors. It's like using a validated kit instead of buying individual reagents and mixing them yourself.

Hello World: Starting a Server in 3 Lines

The most basic form of an Express server:

javascript
const express = require("express");
const app = express();

app.get("/", function(req, res) {
  res.send("LIMS Server Running");
});

app.listen(3000, function() {
  console.log("Server started: http://localhost:3000");
});

Save this as server.js and run node server.js in the terminal. Then visit http://localhost:3000 in your browser.

Breaking down the key elements:

  • require("express") โ€” loads the Express library
  • app.get("/", ...) โ€” registers a function to run when someone visits "/"
  • req โ€” request. Information sent by the client
  • res โ€” response. Information the server will send back
  • res.send() โ€” sends data to the client
  • app.listen(3000) โ€” listens for requests on port 3000

Routing: Serving Different Data by URL

Routing is deciding "when a request comes to this URL, what data should we send back?" Like entering a sample ID in LIMS and getting that sample's info, or entering a protocol name and getting that protocol.

javascript
const express = require("express");
const app = express();

app.get("/", function(req, res) {
  res.send("Welcome to the Experiment Data Server");
});

app.get("/genes", function(req, res) {
  const genes = [
    { name: "TP53", chromosome: "17p13.1", type: "tumor suppressor" },
    { name: "BRCA1", chromosome: "17q21.31", type: "DNA repair" },
    { name: "EGFR", chromosome: "7p11.2", type: "receptor tyrosine kinase" }
  ];
  res.json(genes);
});

app.get("/protocols", function(req, res) {
  const protocols = ["DNA Extraction", "PCR", "Western Blot", "ELISA"];
  res.json(protocols);
});

app.listen(3000, function() {
  console.log("Server started: http://localhost:3000");
});

Visit http://localhost:3000/genes and you'll see the gene list; visit /protocols and you'll see the protocol list as JSON. res.json() converts JavaScript objects to JSON format and sends them.

Dynamic Routing: Getting Parameters from URLs

To look up information for a specific gene, put a parameter in the URL:

javascript
const geneDatabase = {
  TP53: { name: "TP53", chromosome: "17p13.1", function: "tumor suppressor", length_bp: 19149 },
  BRCA1: { name: "BRCA1", chromosome: "17q21.31", function: "DNA repair", length_bp: 81189 },
  EGFR: { name: "EGFR", chromosome: "7p11.2", function: "receptor tyrosine kinase", length_bp: 188307 }
};

app.get("/gene/:id", function(req, res) {
  const geneId = req.params.id;
  const gene = geneDatabase[geneId];

  if (gene) {
    res.json(gene);
  } else {
    res.status(404).json({ error: "Gene not found", query: geneId });
  }
});

Visit /gene/TP53 and you get TP53's info; visit /gene/XYZ123 and you get a 404 error. The :id part is a URL parameter โ€” a different value comes in with each request, and you extract it with req.params.id.

Middleware: Pre-processing Steps

Middleware is a core Express concept. It's a processing step that runs between when a request arrives at the server and when the response goes out.

Think of the pre-processing steps a sample goes through before entering an analysis instrument:

text
[Sample received] โ†’ [Label check] โ†’ [QC test] โ†’ [Pre-treatment] โ†’ [Analysis] โ†’ [Report]

In Express, requests go through a similar pipeline:

text
[Request arrives] โ†’ [Middleware 1] โ†’ [Middleware 2] โ†’ [Route handler] โ†’ [Response sent]

Middleware is registered with app.use():

javascript
const express = require("express");
const app = express();

app.use(function(req, res, next) {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});

app.get("/genes", function(req, res) {
  res.json([{ name: "TP53" }, { name: "BRCA1" }]);
});

app.listen(3000);

The function registered with app.use() runs on every request. Here it logs the time, method (GET/POST), and URL for every incoming request. Like noting "what time, which sample, which analysis" in your lab notebook.

The key is next(). You must call this function to proceed to the next step (next middleware or route handler). Without calling next(), the request stops here and no response is sent โ€” like a sample that fails QC and doesn't move to the next step.

Practical Middleware: Parsing POST Data

When a client sends data via POST (e.g., registering a new sample), you need to parse that data to read it. Express provides built-in middleware for this:

javascript
app.use(express.json());

app.post("/samples", function(req, res) {
  const newSample = req.body;
  console.log("New sample registered:", newSample);
  res.json({ message: "Sample registration complete", sample: newSample });
});

Without express.json(), req.body would be undefined. This middleware automatically parses incoming JSON data and puts it in req.body.

Full Example: Sample Management API

Let's combine everything learned so far to build a simple sample management server:

javascript
const express = require("express");
const app = express();

app.use(express.json());

app.use(function(req, res, next) {
  console.log(`[LOG] ${req.method} ${req.url}`);
  next();
});

const samples = [
  { id: "S001", name: "Blood Sample A", od: 1.85, status: "pass" },
  { id: "S002", name: "Tissue Sample B", od: 0.42, status: "fail" },
  { id: "S003", name: "Serum Sample C", od: 2.10, status: "pass" }
];

app.get("/samples", function(req, res) {
  res.json(samples);
});

app.get("/sample/:id", function(req, res) {
  const found = samples.find(function(s) {
    return s.id === req.params.id;
  });

  if (found) {
    res.json(found);
  } else {
    res.status(404).json({ error: "Sample not found" });
  }
});

app.get("/samples/passed", function(req, res) {
  const passed = samples.filter(function(s) {
    return s.status === "pass";
  });
  res.json({ count: passed.length, samples: passed });
});

app.listen(3000, function() {
  console.log("Sample management server started: http://localhost:3000");
});

This server provides three API endpoints:

  • GET /samples โ€” full sample list
  • GET /sample/S001 โ€” look up a specific sample
  • GET /samples/passed โ€” filter QC-passed samples only

Using fetch from the earlier topic to call this server:

javascript
fetch("http://localhost:3000/samples/passed")
  .then(function(response) { return response.json(); })
  .then(function(data) { console.log(data); });
// { count: 2, samples: [{ id: "S001", ... }, { id: "S003", ... }] }

This is the moment frontend (fetch) and backend (Express) connect. It's a miniature version of what happens behind the scenes when you search for genes on NCBI.

Try It Yourself (Faded Example)

Fill in the blanks to complete an Express route that returns gene information.

Fill in the Blanksjavascript
const express = require("express");
const app = express();
app.get("/gene/", function(req, res) {
const geneId = req..id;
res.json({ gene: geneId, found: true });
});
app.listen();

Common Errors & Solutions

Q: Cannot GET /path error

No route is registered for that path. Check that app.get("/path", ...) exists and has no typos. Express automatically sends a Cannot GET message when no matching route is found.

Q: req.body is undefined

Make sure app.use(express.json()) is declared above your routes. Middleware executes in code order. If the JSON parsing middleware is after the route, the request reaches the route before parsing happens.

Q: Changes to the server aren't reflected

Node.js doesn't auto-reload when code changes. Stop the server with Ctrl+C in the terminal and restart with node server.js. For auto-restart, install nodemon and run nodemon server.js.

Q: Error: listen EADDRINUSE: address already in use :::3000

Another program is already using port 3000. A previously started server might still be running. Check with lsof -i :3000 and terminate it, or change the port number to 3001, etc.

Q: Express is called a "framework" โ€” how is it different from other npm packages (like mysql2)?

The key difference is who calls whom. mysql2 is a library โ€” you call connection.query() when you need it. Express is a framework โ€” you register your code with app.get("/path", callback), and when a request arrives, Express calls your callback.

This concept is called "Inversion of Control." For a more detailed analogy and explanation, see the Framework vs Library topic.

๐Ÿ’ฌ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...