Let’s create an endpoint to add new users to the database. This uses the same INSERT INTO command we learned in Lesson 1, but now it’s inside a POST API endpoint.
Returning the Created User
When creating resources, it’s standard practice to return the newly created object back to the client. When you’ve used APIs from the frontend before, you’ve probably noticed that when you POST to create something, the API responds with a 201 status code and the complete created object (including the generated ID).
This is exactly what we’ll implement: after inserting the user into the database, we’ll construct the user object using the returned insertId and the original request data to return it to the frontend. This allows the frontend to immediately display or work with the new user data without making a separate GET request.
First we need to import this with the rest of the imports:
import { ResultSetHeader } from "mysql2";
Creating Users Endpoint
// Create a new user
app.post("/users", async (req, res) => {
try {
const { username, email } = req.body;
// Basic validation
if (!username || !email) {
return res.status(400).json({
error: "Username and email are required",
});
}
// Insert the new user into the database
const [result]: [ResultSetHeader, any] = await pool.execute(
"INSERT INTO users (username, email) VALUES (?, ?)",
[username, email]
);
const user: User = { id: result.insertId, username, email };
res.status(201).json(user);
} catch (error) {
console.error("Database error:", error);
res.status(500).json({
error: "Failed to create user",
});
}
});
Understanding the POST Code
How the Code Works
- INSERT the user: We add the new user to the database with
INSERT INTO. - Construct the response object: We build the user object from the request data and the generated ID from
result.insertId.
Understanding the Database Result
When we execute an INSERT query, MySQL returns useful information about what happened:
const [result]: [ResultSetHeader, any] = await pool.execute(...);
What’s happening here
- Array destructuring:
const [result]takes the first element from the returned array. - Type annotation:
[ResultSetHeader, any]tells TypeScript what types to expect. - ResultSetHeader contains:
insertId- The auto-generated ID of the new row (this is what we need).affectedRows- How many rows were changed (should be 1 for successful insert).changedRows- How many rows actually changed.
Building the Response
const user: User = { id: result.insertId, username, email };
We create the user object by combining:
id: The database-generated ID fromresult.insertId.usernameandemail: The original data from the request body.
Without the [ResultSetHeader, any] type annotation, TypeScript wouldn’t know that result has an insertId property.
Response
- Return 201 Created status code for successful creation.
- Return the complete user object including the database-generated ID.